How PGSQL can facilitate the data processing workflow

Parfait Gasana

Data Analyst, Winston & Strawn
## Libraries
library(DBI)
Loading required package: methods
library(RPostgreSQL)
library(microbenchmark)

library(scales)
library(ggplot2)

options(repr.plot.width=15, repr.plot.height=5)
options(scipen=999)

Import CSV Data

csv_import <- function() {
    setwd("~/Documents/PGSQL")
    
    bus_month <- read.csv('CTA_-_Ridership_-_Bus_Routes_-_Monthly_Day-Type_Averages___Totals.csv')
    bus_rides <- read.csv('CTA_-_Ridership_-_Bus_Routes_-_Daily_Totals_by_Route.csv')

    rail_stops <- read.csv('CTA_-_System_Information_-_List_of__L__Stops.csv')
    rail_rides <- read.csv('CTA_-_Ridership_-__L__Station_Entries_-_Daily_Totals.csv')
}

Import SQL Data

sql_import <- function() {
    conn <- dbConnect(RPostgreSQL::PostgreSQL(), host="10.0.0.220", dbname="cta",
                      user="ctadba", password="cta18!", port=5432)

    bus_month <- dbGetQuery(conn, "SELECT * FROM bus_month")
    bus_rides <- dbGetQuery(conn, "SELECT * FROM bus_rides")

    rail_stops <- dbGetQuery(conn, "SELECT * FROM rail_stops")
    rail_rides <- dbGetQuery(conn, "SELECT * FROM rail_rides")

    dbDisconnect(conn)
}

summary(microbenchmark(csv_import))
summary(microbenchmark(sql_import))

View CSV Data

bus_month_csv <- read.csv('CTA_-_Ridership_-_Bus_Routes_-_Monthly_Day-Type_Averages___Totals.csv')
head(bus_month_csv)
bus_rides_csv <- read.csv('CTA_-_Ridership_-_Bus_Routes_-_Daily_Totals_by_Route.csv')
head(bus_rides_csv)
rail_stops_csv <- read.csv('CTA_-_System_Information_-_List_of__L__Stops.csv')
names(rail_stops_csv) <- tolower(names(rail_stops_csv))
head(rail_stops_csv)
rail_rides_csv <- read.csv('CTA_-_Ridership_-__L__Station_Entries_-_Daily_Totals.csv')
head(rail_rides_csv)
bus_month_csv$Month_Beginning <- as.Date(bus_month_csv$Month_Beginning, format="%m/%d/%Y", origin="1970-01-01")

bus_rides_csv$date <- as.Date(bus_rides_csv$date, format="%m/%d/%Y", origin="1970-01-01")

rail_rides_csv$date <- as.Date(rail_rides_csv$date, format="%m/%d/%Y", origin="1970-01-01")

View SQL Data

conn <- dbConnect(RPostgreSQL::PostgreSQL(), host="10.0.0.220", dbname="cta",
                  user="ctadba", password="cta18!", port=5432)

bus_month_sql <- dbGetQuery(conn, "SELECT * FROM bus_month")
head(bus_month_sql)
bus_rides_sql <- dbGetQuery(conn, "SELECT * FROM bus_rides")
head(bus_rides_sql)
rail_stations_sql <- dbGetQuery(conn, "SELECT * FROM rail_stations")
head(rail_stations_sql)
rail_rides_sql <- dbGetQuery(conn, "SELECT * FROM rail_rides")
head(rail_rides_sql)

Aggregate CSV Data

# MERGE
agg_csv <- merge(unique(bus_month_csv[c("route", "routename")]), bus_rides_csv, by="route")

# AGGREGATE
agg_csv <- do.call(data.frame, 
                   aggregate(rides ~ route + routename, agg_csv, 
                             function(x) c(count=length(x), sum=sum(x), mean=mean(x), 
                                           median=median(x), min=min(x), max=max(x))))
# ORDER
agg_csv <- with(agg_csv, agg_csv[order(-rides.sum),])

agg_csv

Top 5 Bus Routes

# MERGE
agg_csv <- merge(subset(unique(bus_month_csv[c("route", "routename")]), 
                        routename %in% c("79th", "Ashland", "Chicago", "Western", "Cottage Grove")),
                 transform(bus_rides_csv, year=format(date, "%Y")),
                 by="route")

# AGGREGATE
agg_csv <- do.call(data.frame, 
                   aggregate(rides ~ routename + year, agg_csv, 
                             function(x) c(count=length(x), sum=sum(x), mean=mean(x), 
                                           median=median(x), min=min(x), max=max(x))))
# ORDER
agg_csv <- with(agg_csv, agg_csv[order(routename, year),])

agg_csv

Graph CSV Data

seabornPalette <- c("#4c72b0","#55a868","#c44e52","#8172b2","#ccb974","#64b5cd","#4c72b0","#55a868",
                    "#c44e52","#8172b2","#ccb974","#64b5cd","#4c72b0","#55a868","#c44e52","#8172b2",
                    "#ccb974","#64b5cd","#4c72b0","#55a868","#c44e52","#8172b2","#ccb974","#64b5cd",
                    "#4c72b0","#55a868","#c44e52","#8172b2","#ccb974","#64b5cd","#4c72b0","#55a868",
                    "#c44e52","#8172b2","#ccb974","#64b5cd","#4c72b0","#55a868","#c44e52","#8172b2",
                    "#ccb974","#64b5cd","#4c72b0","#55a868","#c44e52","#8172b2","#ccb974","#64b5cd",
                    "#4c72b0","#55a868","#c44e52","#8172b2","#ccb974","#64b5cd","#4c72b0","#55a868",
                    "#c44e52","#8172b2","#ccb974","#64b5cd")

ggplot(agg_csv, aes(year, rides.sum, fill=routename)) + geom_col(position = "dodge") +
  labs(title="Top 5 CTA 'L' Bus Routes", x="Year", y="Rides") +
  scale_y_continuous(expand = c(0, 0), label=comma) +
  scale_fill_manual(values = seabornPalette) +
  theme(legend.position="bottom",
        plot.title = element_text(hjust=0.5, size=18),
        axis.text.x = element_text(angle=0, hjust=0.5))

Aggregate SQL Data

sql <- 'SELECT rt.route_name, COUNT(rd.rides) AS "count", 
                              SUM(rd.rides) AS "sum", 
                              AVG(rd.rides) AS "mean", 
                              MEDIAN(rd.rides) AS "median",
                              MIN(rd.rides) AS "min", 
                              MAX(rd.rides) AS "max"
        FROM bus_routes rt
        INNER JOIN bus_rides rd ON rt.route_id = rd.route_id
        GROUP BY rt.route_name
        ORDER BY SUM(rd.rides) DESC'

agg_sql <- dbGetQuery(conn, sql)

agg_sql
sql <- 'SELECT rt.route_name, DATE_PART(\'year\', rd.ride_date)::integer AS "year", 
             COUNT(rd.rides) AS "count", 
             SUM(rd.rides) AS "sum", 
             AVG(rd.rides) AS "mean", 
             MEDIAN(rd.rides) AS "median",
             MIN(rd.rides) AS "min", 
             MAX(rd.rides) AS "max"
      FROM bus_routes rt
      INNER JOIN bus_rides rd ON rt.route_id = rd.route_id
      WHERE rt.route_name IN (\'79th\', \'Ashland\', \'Chicago\', \'Western\', \'Cottage Grove\')
      GROUP BY rt.route_name, DATE_PART(\'year\', rd.ride_date)::integer
      ORDER BY rt.route_name, DATE_PART(\'year\', rd.ride_date)::integer'

agg_sql <- dbGetQuery(conn, sql)

agg_sql

Graph SQL Data

ggplot(agg_sql, aes(year, sum, color=route_name)) + 
  geom_line(stat="identity") + geom_point(stat="identity") +
  labs(title="Top 5 CTA 'L' Bus Routes", x="Year", y="Rides") +
  scale_x_continuous("year", breaks=unique(agg_sql$year)) +
  scale_y_continuous(expand = c(0, 0), label=comma) +
  scale_color_manual(values = seabornPalette) +
  theme(legend.position="bottom",
        plot.title = element_text(hjust=0.5, size=18),
        axis.text.x = element_text(angle=0, hjust=0.5))

CSV Data Diagnostics

# TRANSFORM
agg_csv <- transform(rail_rides_csv, year=format(date, "%Y"))

# AGGREGATE
agg_csv <- do.call(data.frame, 
                   aggregate(rides ~ station_id + stationname + year, agg_csv, 
                             function(x) c(count=length(x), sum=sum(x), mean=mean(x), 
                                           median=median(x), min=min(x), max=max(x))))
# ORDER
agg_csv <- with(agg_csv, agg_csv[order(-rides.sum),])

agg_csv
# RESHAPE
rail_lines <- c("red", "blue", "green", "brown", "purple", "purple_exp", "yellow", "pink", "orange")

rail_long <- reshape(setNames(rail_stops_csv[c("map_id", "station_name", "location", "red", "blue", "g", 
                                               "brn", "p", "pexp", "y", "pnk", "o")],
                              c("station_id", "station_name", "location", rail_lines)),
                     varying = rail_lines, v.names = "value", 
                     timevar = "rail_line", times = rail_lines,
                     new.row.names = 1:10000, direction = "long")

# SUBSET
rail_long <- unique(subset(rail_long, value=='true')[c("station_id", "station_name", "location", "rail_line")])

# ORDER
rail_long <- with(rail_long, rail_long[order(station_id, rail_line),])
row.names(rail_long) <- NULL

rail_long
merge_rail <- merge(agg_csv, rail_long, by="station_id")

merge_rail$rides.total <- merge_rail$rides.sum / with(merge_rail, ave(station_id, station_id, year, FUN=length))

merge_rail
agg_csv <- aggregate(rides.total ~ year + rail_line, merge_rail, sum)
agg_csv <- with(agg_csv, agg_csv[order(rail_line, year),])
row.names(agg_csv) <- NULL

agg_csv

SQL Data Diagnostics

sql <- 'WITH station_agg AS
         (SELECT DATE_PART(\'year\', r.ride_date)::integer AS "year",
                 r.station_id,
                 r.station_name,
                 COUNT(r.rides)::numeric(20,5) AS "count", 
                 SUM(r.rides)::numeric(20,5) AS "sum", 
                 AVG(r.rides)::numeric(20,5) AS "mean", 
                 MEDIAN(r.rides)::numeric(20,5) AS "median",
                 MIN(r.rides)::numeric(20,5) AS "min", 
                 MAX(r.rides)::numeric(20,5) AS "max"
          FROM rail_rides r
          GROUP BY DATE_PART(\'year\', r.ride_date),
                   r.station_id,
                   r.station_name
          ),
                   
      merge_rail AS
         (SELECT s.*, 
                 r.rail_line,
                 (s."sum" / COUNT(*) OVER(PARTITION BY s.station_id, "year")) AS rail_total
          FROM station_agg s
          INNER JOIN rail_stations r ON s.station_id = r.station_id
         )
         
      SELECT m."year", m.rail_line, SUM(m.rail_total)  AS rail_total
      FROM merge_rail m
      GROUP BY m."year", m.rail_line
      ORDER BY m.rail_line, m."year"'
  
agg_sql <- dbGetQuery(conn, sql)

agg_sql
cta_palette <- c(blue="#00A1DE", brown="#62361B", green="#009B3A", orange="#F9461C", pink="#E27EA6",
                 purple="#522398", purple_exp="#8059BA", red="#C60C30", yellow="#F9E300")

ggplot(subset(agg_sql, year > 2012), aes(year, rail_total, fill=rail_line)) + geom_col(position = "dodge") +
  labs(title="CTA System 'L' Lines - Total Rides By Year", x="Year", y="Rides") +
  scale_x_continuous(expand = c(0,0), "year", breaks=unique(agg_sql$year)) +
  scale_y_continuous(expand = c(0, 0), label=comma) +
  scale_fill_manual(values = cta_palette) + guides(fill=guide_legend("Rail Lines", nrow=1)) +
  theme(legend.position="bottom",
        plot.title = element_text(hjust=0.5, size=18),
        axis.text.x = element_text(angle=0, hjust=0.5))

Distribution

sql <- 'SELECT r.station_id, r.ride_date, r.station_name, s.rail_line, r.rides,
               (r.rides / COUNT(*) OVER(PARTITION BY s.station_id, r.ride_date)) AS rides_total
        FROM rail_rides r
        INNER JOIN rail_stations s ON s.station_id = r.station_id'

hist_long <- dbGetQuery(conn, sql)

hist_long
ggplot(hist_long, aes(x=rides_total, fill=rail_line)) +
   geom_histogram(data=subset(hist_long, rail_line == 'red'), bins=100) +
   geom_histogram(data=subset(hist_long, rail_line == 'blue'), bins=100) +
   geom_histogram(data=subset(hist_long, rail_line == 'brown'), bins=100) +
   geom_histogram(data=subset(hist_long, rail_line == 'green'), bins=100) +
   geom_histogram(data=subset(hist_long, rail_line == 'orange'), bins=100) +
   scale_x_continuous(expand = c(0, 0)) +
   scale_y_continuous(expand = c(0, 0), lim=c(0,40000), label=comma) +
   scale_fill_manual(values = c(red = "#C60C30", blue = "#00A1DE", brown = "#62361B",
                                green = "#009B3A", orange = "#F9461C")) +
   labs(title="CTA Ridership Distribution By Rail Line", fill="Rail Line") +
   theme(plot.title = element_text(hjust=0.5, size=18))

ggplot(transform(hist_long, year = format(ride_date, '%Y')), 
       aes(x=year, y=rides_total, fill=year)) + 
    geom_boxplot() + guides(fill=FALSE) +
    scale_y_continuous(expand = c(0, 0), lim=c(0,40000), label=comma) +
    labs(title="CTA Ridership Boxplot By Year") +
    theme(plot.title = element_text(hjust=0.5, size=18))

ggplot(hist_long, aes(x=rail_line, y=rides_total, fill=rail_line)) + 
    geom_boxplot() + guides(fill=FALSE) +
    scale_fill_manual(values = cta_palette) +
    scale_y_continuous(expand = c(0, 0), lim=c(0,40000), label=comma) +
    labs(title="CTA Ridership Boxplot By Rail Line") +
    theme(plot.title = element_text(hjust=0.5, size=18))

ggplot(subset(within(hist_long, { year <- format(ride_date, '%Y')
                                  year <- as.integer(as.character(year))
                                }),
              year >= 2015 & year <= 2017), 
       aes(x=factor(year), y=rides_total, fill=rail_line)) + 
    geom_boxplot() + guides(fill=guide_legend("Rail Lines", nrow=1)) +
    scale_fill_manual(values = cta_palette) +
    scale_x_discrete(expand = c(0, 0)) +
    scale_y_continuous(expand = c(0, 0), lim=c(0,40000), label=comma) +
    labs(title="CTA Ridership Boxplot By Rail Line, 2015-2017", x="Year", y="Rides") +
    theme(legend.position="bottom", plot.title = element_text(hjust=0.5, size=18))

Correlation

sql <- 'WITH station_agg AS
         (SELECT DATE_PART(\'year\', r.ride_date)::integer AS "year",
                 r.station_id,
                 r.station_name,
                 COUNT(r.rides)::numeric(20,5) AS "count", 
                 SUM(r.rides)::numeric(20,5) AS "sum", 
                 AVG(r.rides)::numeric(20,5) AS "mean", 
                 MEDIAN(r.rides)::numeric(20,5) AS "median",
                 MIN(r.rides)::numeric(20,5) AS "min", 
                 MAX(r.rides)::numeric(20,5) AS "max"
          FROM rail_rides r
          GROUP BY DATE_PART(\'year\', r.ride_date),
                   r.station_id,
                   r.station_name
          ),
                   
      merge_rail AS
         (SELECT s.*, 
                 r.rail_line,
                 (s."sum" / COUNT(*) OVER(PARTITION BY s.station_id, "year")) AS rail_total
          FROM station_agg s
          INNER JOIN rail_stations r ON s.station_id = r.station_id
         )
         
      SELECT m."year",
             SUM(rail_total) FILTER (WHERE rail_line = \'blue\') AS blue,
             SUM(rail_total) FILTER (WHERE rail_line = \'brown\') AS brown,
             SUM(rail_total) FILTER (WHERE rail_line = \'green\') AS green,
             SUM(rail_total) FILTER (WHERE rail_line = \'orange\') AS orange,
             SUM(rail_total) FILTER (WHERE rail_line = \'pink\') AS pink,
             SUM(rail_total) FILTER (WHERE rail_line = \'purple\') AS purple,
             SUM(rail_total) FILTER (WHERE rail_line = \'purple_exp\') AS purple_exp,
             SUM(rail_total) FILTER (WHERE rail_line = \'red\') AS red,
             SUM(rail_total) FILTER (WHERE rail_line = \'yellow\') AS yellow
      FROM merge_rail m
      GROUP BY m."year"
      ORDER BY m."year"'

wide_sql <- dbGetQuery(conn, sql)

wide_sql
cor(wide_sql[-1], use = "complete.obs", method="pearson")
                blue     brown     green    orange      pink    purple
blue       1.0000000 0.9735257 0.8909543 0.9490871 0.8764534 0.7780920
brown      0.9735257 1.0000000 0.8839102 0.9165204 0.8888845 0.7446475
green      0.8909543 0.8839102 1.0000000 0.9295203 0.8768352 0.7684971
orange     0.9490871 0.9165204 0.9295203 1.0000000 0.8697865 0.8742380
pink       0.8764534 0.8888845 0.8768352 0.8697865 1.0000000 0.5573137
purple     0.7780920 0.7446475 0.7684971 0.8742380 0.5573137 1.0000000
purple_exp 0.9707673 0.9785738 0.9264667 0.9722522 0.8994040 0.8304641
red        0.8931410 0.8892051 0.7947539 0.9334301 0.7611228 0.9142396
yellow     0.7415022 0.7744762 0.7812455 0.7698821 0.6214899 0.7375434
           purple_exp       red    yellow
blue        0.9707673 0.8931410 0.7415022
brown       0.9785738 0.8892051 0.7744762
green       0.9264667 0.7947539 0.7812455
orange      0.9722522 0.9334301 0.7698821
pink        0.8994040 0.7611228 0.6214899
purple      0.8304641 0.9142396 0.7375434
purple_exp  1.0000000 0.9355244 0.7854706
red         0.9355244 1.0000000 0.7241108
yellow      0.7854706 0.7241108 1.0000000

T-tests

combns <- as.list(outer(rail_lines, rail_lines, function(x, y) ifelse(x==y, NA, paste(x, y))))
combns <- lapply(combns[!is.na(combns)], function(x) strsplit(x, split=" ")[[1]])

ttest_matrix <- sapply(combns, function(x){
  t <- t.test(wide_sql[[x[1]]], wide_sql[[x[2]]])
  c(statistic = t$statistic, p_value = t$p.value)
  
})

ttest_df <- data.frame(x = sapply(combns, "[", 1),
                       y = sapply(combns, "[", 2),
                       statistic = ttest_matrix[1,],
                       p_value = ttest_matrix[2,])

ttest_df <- with(ttest_df, ttest_df[order(x, y),])
ttest_df
by(ttest_df, ttest_df$x, function(sub)
  
  ggplot(sub, aes(x, statistic, fill=y)) + geom_col(position = "dodge") +
    labs(title=paste0("CTA System 'L' Lines - Pairwise T-tests for ", 
                     toupper(substr(sub$x[[1]], 1, 1)), 
                     substr(sub$x[[1]], 2, nchar(as.character(sub$x[[1]]))), " Line"), 
         x="Rail Line", y="T-test Stat") +
    scale_x_discrete(expand = c(0,0)) + 
    scale_y_continuous(expand = c(0, 0), label=comma) +
    scale_fill_manual(values = cta_palette[!names(cta_palette)==sub$x[[1]]]) + 
    guides(fill=guide_legend("Rail Lines", nrow=1)) +
    theme(legend.position="bottom",
          plot.title = element_text(hjust=0.5, size=18, colour=cta_palette[names(cta_palette)==sub$x[[1]]]),
          axis.text.x = element_text(angle=0, hjust=0.5))
  )
ttest_df$x: blue

-------------------------------------------------------- 
ttest_df$x: brown

-------------------------------------------------------- 
ttest_df$x: green

-------------------------------------------------------- 
ttest_df$x: orange

-------------------------------------------------------- 
ttest_df$x: pink

-------------------------------------------------------- 
ttest_df$x: purple

-------------------------------------------------------- 
ttest_df$x: purple_exp

-------------------------------------------------------- 
ttest_df$x: red

-------------------------------------------------------- 
ttest_df$x: yellow

CSV Regression

Right-Hand Side Variables

  • Again, requires maintenance and storage on disk space

  • Again, requires load time from non-centralized paths

  • Again, requires any ad-hoc munging for usability

# Source: St. Louis Federal Reserve Bank
unemployment_df <- read.csv('Chicago_Unemployment_Rates.csv')
unemployment_df$Date <- as.Date(unemployment_df$Date, format='%Y-%m-%d', origin='1970-01-01')
head(unemployment_df)
# Source: U.S. Department of Energy: EIA
gas_prices_df <- read.csv('US_Gas_Prices.csv')
gas_prices_df$Date <- as.Date(gas_prices_df$Date, format='%Y-%m-%d', origin='1970-01-01')
head(gas_prices_df)
# Source: U.S. National Oceanic and Atmospheric Administration (NOAA)
weather_df <- read.csv('Chicago_Weather_Data.csv')
weather_df$Date <- as.Date(weather_df$Date, format='%Y-%m-%d', origin='1970-01-01')
head(weather_df)

Bus Model Data

bus_model_data <- merge(unique(bus_month_csv[c("route", "routename")]), bus_rides_csv, by="route")

bus_model_data <- merge(bus_model_data, unemployment_df, by.x='date', by.y='Date')
bus_model_data <- merge(bus_model_data, gas_prices_df, by.x='date', by.y='Date')
bus_model_data <- merge(bus_model_data, weather_df, by.x='date', by.y='Date')

head(bus_model_data)

Add Seasons

bus_model_data$normalized_dt <- as.POSIXlt(bus_model_data$date)
bus_model_data$normalized_dt$year <- bus_model_data$normalized_dt$year +
                                        (2099 - as.integer(format(bus_model_data$date, "%Y")))
bus_model_data$normalized_dt <- as.Date(bus_model_data$normalized_dt)


bus_model_data$season <- ifelse(bus_model_data$normalized_dt >= '2099-01-01' & 
                                   bus_model_data$normalized_dt  < '2099-03-19', 'winter',
                                ifelse(bus_model_data$normalized_dt >= '2099-03-20' & 
                                          bus_model_data$normalized_dt  < '2099-06-19', 'spring',
                                       ifelse(bus_model_data$normalized_dt >= '2099-06-20' & 
                                                 bus_model_data$normalized_dt  < '2099-09-19', 'summer',
                                              ifelse(bus_model_data$normalized_dt >= '2099-09-20' & 
                                                        bus_model_data$normalized_dt  < '2099-12-19', 'fall',
                                                     ifelse(bus_model_data$normalized_dt >= '2099-12-20' & 
                                                              bus_model_data$normalized_dt  < '2099-12-31', 'winter',
                                                            NA)
                                              )
                                       )
                                )
                         )
                                       
bus_model_data[sample(nrow(bus_model_data), 10), c("normalized_dt", "date", "season")]
bus_model_data$normalized_dt <- NULL

Bus Modeling (Ordinary Least Squares)

model <- lm(rides ~ daytype + season + UE_Rate + Gas_Price + AvgTemp + Precipitation + SnowDepth,
            data = bus_model_data)

summary(model)

Call:
lm(formula = rides ~ daytype + season + UE_Rate + Gas_Price + 
    AvgTemp + Precipitation + SnowDepth, data = bus_model_data)

Residuals:
   Min     1Q Median     3Q    Max 
 -7661  -4936  -2141   3321  38050 

Coefficients:
                Estimate Std. Error t value             Pr(>|t|)    
(Intercept)    5209.6046    51.7800 100.610 < 0.0000000000000002 ***
daytypeU      -1499.4540    32.9522 -45.504 < 0.0000000000000002 ***
daytypeW        681.4509    25.0499  27.204 < 0.0000000000000002 ***
seasonspring   -255.0354    22.8648 -11.154 < 0.0000000000000002 ***
seasonsummer   -644.0198    28.4167 -22.663 < 0.0000000000000002 ***
seasonwinter   -373.8617    26.7128 -13.996 < 0.0000000000000002 ***
UE_Rate          99.9726     4.1670  23.991 < 0.0000000000000002 ***
Gas_Price       -27.0833    10.4389  -2.594              0.00947 ** 
AvgTemp          11.9167     0.7148  16.671 < 0.0000000000000002 ***
Precipitation  -339.3977    23.2796 -14.579 < 0.0000000000000002 ***
SnowDepth        -9.7763     5.1624  -1.894              0.05826 .  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 6401 on 665624 degrees of freedom
  (148560 observations deleted due to missingness)
Multiple R-squared:  0.01444,   Adjusted R-squared:  0.01443 
F-statistic: 975.5 on 10 and 665624 DF,  p-value: < 0.00000000000000022

Rail Model Data

Assign Latitude and Longitude

rail_long$latitude <- as.numeric(gsub("\\(", "", gsub(",", "", sapply(as.character(rail_long$location), 
                                                         function(x) strsplit(x, split="\\s+")[[1]][1])))
                                )

rail_long$longitude <- as.numeric(gsub("\\)", "", sapply(as.character(rail_long$location), 
                                            function(x) strsplit(x, split="\\s+")[[1]][2]))
                                  )

rail_long[sample(nrow(rail_long), 10), c("location", "latitude", "longitude")]
rail_model_data <- merge(rail_long, rail_rides_csv, by="station_id")

rail_model_data$raw <- rail_model_data$rides

rail_model_data$rides <- with(rail_model_data, rides /
                                  ave(station_id, station_id, date, FUN=length))

rail_model_data <- merge(rail_model_data, unemployment_df, by.x='date', by.y='Date')
rail_model_data <- merge(rail_model_data, gas_prices_df, by.x='date', by.y='Date')
rail_model_data <- merge(rail_model_data, weather_df, by.x='date', by.y='Date')

head(rail_model_data, 10)

Add Seasons

rail_model_data$normalized_dt <- as.POSIXlt(rail_model_data$date)
rail_model_data$normalized_dt$year <- rail_model_data$normalized_dt$year +
                                        (2099 - as.integer(format(rail_model_data$date, "%Y")))
rail_model_data$normalized_dt <- as.Date(rail_model_data$normalized_dt)


rail_model_data$season <- ifelse(rail_model_data$normalized_dt >= '2099-01-01' & 
                                   rail_model_data$normalized_dt  < '2099-03-19', 'winter',
                                ifelse(rail_model_data$normalized_dt >= '2099-03-20' & 
                                          rail_model_data$normalized_dt  < '2099-06-19', 'spring',
                                       ifelse(rail_model_data$normalized_dt >= '2099-06-20' & 
                                                 rail_model_data$normalized_dt  < '2099-09-19', 'summer',
                                              ifelse(rail_model_data$normalized_dt >= '2099-09-20' & 
                                                        rail_model_data$normalized_dt  < '2099-12-19', 'fall',
                                                     ifelse(rail_model_data$normalized_dt >= '2099-12-20' & 
                                                              rail_model_data$normalized_dt  < '2099-12-31', 'winter',
                                                            NA)
                                              )
                                       )
                                )
                         )
                                       
rail_model_data[sample(nrow(rail_model_data), 10), c("normalized_dt", "date", "season")]
rail_model_data$normalized_dt <- NULL

Rail Modeling (Ordinary Least Squares)

model <- lm(rides ~ daytype + season + latitude + longitude + rail_line + 
                    UE_Rate + Gas_Price + AvgTemp + Precipitation + SnowDepth, 
            data = rail_model_data)

summary(model)

Call:
lm(formula = rides ~ daytype + season + latitude + longitude + 
    rail_line + UE_Rate + Gas_Price + AvgTemp + Precipitation + 
    SnowDepth, data = rail_model_data)

Residuals:
    Min      1Q  Median      3Q     Max 
-6252.1  -933.1  -256.4   537.0 30569.2 

Coefficients:
                       Estimate  Std. Error t value             Pr(>|t|)
(Intercept)         -67697.5885   3581.3622  -18.90 < 0.0000000000000002
daytypeU              -453.5025      6.6974  -67.71 < 0.0000000000000002
daytypeW              1160.6410      5.3328  217.64 < 0.0000000000000002
seasonspring          -178.8823      5.3762  -33.27 < 0.0000000000000002
seasonsummer          -189.5721      6.6870  -28.35 < 0.0000000000000002
seasonwinter          -157.0931      6.2731  -25.04 < 0.0000000000000002
latitude             -3995.6159     36.9560 -108.12 < 0.0000000000000002
longitude            -2703.3418     44.1307  -61.26 < 0.0000000000000002
rail_linebrown       -1180.7391      7.2879 -162.01 < 0.0000000000000002
rail_linegreen       -2070.2382      6.9165 -299.32 < 0.0000000000000002
rail_lineorange      -1077.6339      8.3798 -128.60 < 0.0000000000000002
rail_linepink        -2211.9982      7.4465 -297.05 < 0.0000000000000002
rail_linepurple      -2027.7884     11.2705 -179.92 < 0.0000000000000002
rail_linepurple_exp  -1547.7298      7.7477 -199.77 < 0.0000000000000002
rail_linered          1640.2397      6.9847  234.83 < 0.0000000000000002
rail_lineyellow      -1400.7779     17.7956  -78.72 < 0.0000000000000002
UE_Rate                 -5.1186      0.9920   -5.16          0.000000247
Gas_Price              185.7348      2.4565   75.61 < 0.0000000000000002
AvgTemp                  5.0953      0.1679   30.34 < 0.0000000000000002
Precipitation          -82.5539      5.3670  -15.38 < 0.0000000000000002
SnowDepth                1.4842      1.2067    1.23                0.219
                       
(Intercept)         ***
daytypeU            ***
daytypeW            ***
seasonspring        ***
seasonsummer        ***
seasonwinter        ***
latitude            ***
longitude           ***
rail_linebrown      ***
rail_linegreen      ***
rail_lineorange     ***
rail_linepink       ***
rail_linepurple     ***
rail_linepurple_exp ***
rail_linered        ***
rail_lineyellow     ***
UE_Rate             ***
Gas_Price           ***
AvgTemp             ***
Precipitation       ***
SnowDepth              
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 1850 on 1006966 degrees of freedom
  (223613 observations deleted due to missingness)
Multiple R-squared:  0.4033,    Adjusted R-squared:  0.4033 
F-statistic: 3.403e+04 on 20 and 1006966 DF,  p-value: < 0.00000000000000022

SQL Regression

Bus Modeling Data

CREATE MATERIALIZED VIEW Bus_Model_Data AS
    SELECT b.id, b.route_id, b.ride_date, b.day_type, b.rides, r.route_name, 
           CASE 
               WHEN b.normalized_date BETWEEN '2099-01-01' AND '2099-03-19' THEN 'winter'
               WHEN b.normalized_date BETWEEN '2099-03-20' AND '2099-06-19' THEN 'spring'
               WHEN b.normalized_date BETWEEN '2099-06-20' AND '2099-09-19' THEN 'summer'
               WHEN b.normalized_date BETWEEN '2099-09-20' AND '2099-12-19' THEN 'fall'
               WHEN b.normalized_date BETWEEN '2099-12-20' AND '2099-12-31' THEN 'winter'
               ELSE NULL
           END As season,
           ue.ue_rate, g.gas_price, w.avg_temp, w.precipitation, w.snow_depth
    FROM 
     (
      SELECT id, route_id, day_type, rides, ride_date, 
             ride_date + (2099 - date_part('year', ride_date)  ||' year')::interval as normalized_date
      FROM bus_rides
     ) b
    INNER JOIN bus_routes r ON b.route_id = r.route_id
    INNER JOIN unemployment_rates ue ON ue.ue_date = b.ride_date
    INNER JOIN gas_prices g ON g.gas_date = b.ride_date
    INNER JOIN weather_data w ON w.weather_date = b.ride_date
    ORDER BY b.ride_date, NULLIF(regexp_replace(b.route_id, '\D', '', 'g'), '')::int;
    
REFRESH MATERIALIZED VIEW Bus_Model_Data;
bus_model_data <- dbGetQuery(conn, "SELECT * FROM bus_model_data")

head(bus_model_data)

Bus Modeling (Ordinary Least Squares)

model <- lm(rides ~ day_type + season + ue_rate + gas_price + avg_temp + precipitation + snow_depth,
            data = bus_model_data)

summary(model)

Call:
lm(formula = rides ~ day_type + season + ue_rate + gas_price + 
    avg_temp + precipitation + snow_depth, data = bus_model_data)

Residuals:
   Min     1Q Median     3Q    Max 
 -7813  -5072  -2142   3487  37904 

Coefficients:
                Estimate Std. Error t value            Pr(>|t|)    
(Intercept)    5290.1066    52.9123  99.979 <0.0000000000000002 ***
day_typeU     -1512.5779    33.7077 -44.873 <0.0000000000000002 ***
day_typeW       719.1988    25.6034  28.090 <0.0000000000000002 ***
seasonspring   -259.2512    23.5676 -11.000 <0.0000000000000002 ***
seasonsummer   -644.0360    29.1817 -22.070 <0.0000000000000002 ***
seasonwinter   -384.3928    27.1703 -14.148 <0.0000000000000002 ***
ue_rate          99.6607     4.2720  23.329 <0.0000000000000002 ***
gas_price       -19.6097    10.7135  -1.830              0.0672 .  
avg_temp         12.1726     0.7301  16.673 <0.0000000000000002 ***
precipitation  -350.3917    23.6077 -14.842 <0.0000000000000002 ***
snow_depth       -9.6659     5.3147  -1.819              0.0690 .  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 6483 on 648166 degrees of freedom
  (133403 observations deleted due to missingness)
Multiple R-squared:  0.01478,   Adjusted R-squared:  0.01477 
F-statistic: 972.7 on 10 and 648166 DF,  p-value: < 0.00000000000000022

Rail Modeling Data

CREATE MATERIALIZED VIEW Rail_Model_Data AS
    SELECT r.id, r.station_id, r.station_name, r.ride_date, r.day_type, r.rides AS raw, 
          (r.rides / COUNT(*) OVER(PARTITION BY r.station_id, r.ride_date)) AS rides,
          CASE 
               WHEN r.normalized_date BETWEEN '2099-01-01' AND '2099-03-19' THEN 'winter'
               WHEN r.normalized_date BETWEEN '2099-03-20' AND '2099-06-19' THEN 'spring'
               WHEN r.normalized_date BETWEEN '2099-06-20' AND '2099-09-19' THEN 'summer'
               WHEN r.normalized_date BETWEEN '2099-09-20' AND '2099-12-19' THEN 'fall'
               WHEN r.normalized_date BETWEEN '2099-12-20' AND '2099-12-31' THEN 'winter'
               ELSE NULL
           END As season,        
           REPLACE(REPLACE((regexp_split_to_array(s.location, '\s+'))[1], ',', ''), '(', '')::numeric AS latitude,
           REPLACE((regexp_split_to_array(s.location, '\s+'))[2], ')', '')::numeric AS longitude,
           s.rail_line, s.ada, s.direction,
           ue.ue_rate, g.gas_price, w.avg_temp, w.precipitation, w.snow_depth
    FROM 
       (
        SELECT id, station_id, station_name, day_type, rides, ride_date, 
               ride_date + (2099 - date_part('year', ride_date)  ||' year')::interval as normalized_date
        FROM rail_rides
       )r
    INNER JOIN rail_stations s ON s.station_id = r.station_id
    INNER JOIN unemployment_rates ue ON ue.ue_date = r.ride_date
    INNER JOIN gas_prices g ON g.gas_date = r.ride_date
    INNER JOIN weather_data w ON w.weather_date = r.ride_date
    ORDER BY r.ride_date, r.station_id;
    
REFRESH MATERIALIZED VIEW Rail_Model_Data;
rail_model_data <- dbGetQuery(conn, "SELECT * FROM rail_model_data")

head(rail_model_data)

Rail Modeling (Ordinary Least Squares)

model <- lm(rides ~ day_type + season + latitude + longitude + rail_line + 
                    ue_rate + gas_price + avg_temp + precipitation + snow_depth, 
            data = rail_model_data)

summary(model)

Call:
lm(formula = rides ~ day_type + season + latitude + longitude + 
    rail_line + ue_rate + gas_price + avg_temp + precipitation + 
    snow_depth, data = rail_model_data)

Residuals:
    Min      1Q  Median      3Q     Max 
-6249.5  -932.9  -256.2   536.9 30570.4 

Coefficients:
                       Estimate  Std. Error  t value             Pr(>|t|)
(Intercept)         -68149.8607   3556.3642  -19.163 < 0.0000000000000002
day_typeU             -454.4689      6.6549  -68.290 < 0.0000000000000002
day_typeW             1157.9025      5.2941  218.717 < 0.0000000000000002
seasonspring          -177.8150      5.3603  -33.172 < 0.0000000000000002
seasonsummer          -185.4726      6.6439  -27.916 < 0.0000000000000002
seasonwinter          -160.7995      6.1740  -26.044 < 0.0000000000000002
latitude             -3995.1405     36.6987 -108.863 < 0.0000000000000002
longitude            -2708.3027     43.8227  -61.801 < 0.0000000000000002
rail_linebrown       -1180.4292      7.2371 -163.109 < 0.0000000000000002
rail_linegreen       -2069.6169      6.8682 -301.334 < 0.0000000000000002
rail_lineorange      -1077.4023      8.3213 -129.476 < 0.0000000000000002
rail_linepink        -2211.5330      7.3945 -299.076 < 0.0000000000000002
rail_linepurple      -2027.3643     11.1919 -181.146 < 0.0000000000000002
rail_linepurple_exp  -1547.6652      7.6936 -201.163 < 0.0000000000000002
rail_linered          1641.3142      6.9360  236.637 < 0.0000000000000002
rail_lineyellow      -1400.4416     17.6707  -79.252 < 0.0000000000000002
ue_rate                 -5.3018      0.9843   -5.387         0.0000000718
gas_price              185.5411      2.4399   76.046 < 0.0000000000000002
avg_temp                 5.0833      0.1659   30.635 < 0.0000000000000002
precipitation          -83.1416      5.2864  -15.727 < 0.0000000000000002
snow_depth               1.6746      1.2015    1.394                0.163
                       
(Intercept)         ***
day_typeU           ***
day_typeW           ***
seasonspring        ***
seasonsummer        ***
seasonwinter        ***
latitude            ***
longitude           ***
rail_linebrown      ***
rail_linegreen      ***
rail_lineorange     ***
rail_linepink       ***
rail_linepurple     ***
rail_linepurple_exp ***
rail_linered        ***
rail_lineyellow     ***
ue_rate             ***
gas_price           ***
avg_temp            ***
precipitation       ***
snow_depth             
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 1850 on 1021214 degrees of freedom
  (209365 observations deleted due to missingness)
Multiple R-squared:  0.4031,    Adjusted R-squared:  0.4031 
F-statistic: 3.449e+04 on 20 and 1021214 DF,  p-value: < 0.00000000000000022
# DISCONNECT FROM DATABASE
dbDisconnect(conn)
[1] TRUE

Conclusions

LS0tCnRpdGxlOiAiTGV2ZXJhZ2luZyBQb3N0Z3JlU1FMIGluIERhdGEgU2NpZW5jZSB3aXRoIFIiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCjxkaXYgc3R5bGU9ImZsb2F0OnJpZ2h0Ij48aW1nIHNyYz0icG9zdGdyZXNxbF9yLnBuZyIgaGVpZ2h0PSIyMDAiIHdpZHRoPSIyMDAiLz48L2Rpdj4KCjxkaXYgc3R5bGU9ImZvbnQtc2l6ZTogMjRweDsiPkhvdyBQR1NRTCBjYW4gZmFjaWxpdGF0ZSB0aGUgZGF0YSBwcm9jZXNzaW5nIHdvcmtmbG93PC9kaXY+CiMjIFBhcmZhaXQgR2FzYW5hICMjCjxkaXYgc3R5bGU9ImZvbnQtc2l6ZTogMjBweDsiPkRhdGEgQW5hbHlzdCwgV2luc3RvbiAmIFN0cmF3bjwvZGl2PgoKPHN0eWxlPgpkaXYuYmx1ZSBwcmUgeyBiYWNrZ3JvdW5kLWNvbG9yOiAjRUJGNEZBOyB9Cjwvc3R5bGU+CgpgYGB7cn0KIyMgTGlicmFyaWVzCmxpYnJhcnkoREJJKQpsaWJyYXJ5KFJQb3N0Z3JlU1FMKQpsaWJyYXJ5KG1pY3JvYmVuY2htYXJrKQoKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkoZ2dwbG90MikKCm9wdGlvbnMocmVwci5wbG90LndpZHRoPTE1LCByZXByLnBsb3QuaGVpZ2h0PTUpCm9wdGlvbnMoc2NpcGVuPTk5OSkKCmBgYAoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5JbXBvcnQgQ1NWIERhdGE8L3NwYW4+CgotICMjIyA8c3BhbiBzdHlsZT0iY29sb3I6ICM2NDY0NjQiPk9mdGVuIG9uZSBvZiB0aGUgbGFyZ2VzdCB0aW1lIGFuZCByZXNvdXJjZS1pbnRlbnNpdmUgc3RlcHMgZm9yIGRhdGEgYW5hbHlzdHM8L3NwYW4+ICMjIwotICMjIyA8c3BhbiBzdHlsZT0iY29sb3I6ICM2NDY0NjQiPlJlcXVpcmVzIGRpc2sgc3BhY2Ugb24gc2hhcmVkIG5ldHdvcmsgb3IgbG9jYWwgZHJpdmVzPC9zcGFuPiAjIyMKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5NdWx0aXBsZSBmaWxlcyB1c3VhbGx5IHVubWFuYWdlZCBhY3Jvc3MgdmVyc2lvbnMgYW5kIHRlYW1zPC9zcGFuPiAjIyMKCmBgYHtyfQpjc3ZfaW1wb3J0IDwtIGZ1bmN0aW9uKCkgewogICAgc2V0d2QoIn4vRG9jdW1lbnRzL1BHU1FMIikKICAgIAogICAgYnVzX21vbnRoIDwtIHJlYWQuY3N2KCdDVEFfLV9SaWRlcnNoaXBfLV9CdXNfUm91dGVzXy1fTW9udGhseV9EYXktVHlwZV9BdmVyYWdlc19fX1RvdGFscy5jc3YnKQogICAgYnVzX3JpZGVzIDwtIHJlYWQuY3N2KCdDVEFfLV9SaWRlcnNoaXBfLV9CdXNfUm91dGVzXy1fRGFpbHlfVG90YWxzX2J5X1JvdXRlLmNzdicpCgogICAgcmFpbF9zdG9wcyA8LSByZWFkLmNzdignQ1RBXy1fU3lzdGVtX0luZm9ybWF0aW9uXy1fTGlzdF9vZl9fTF9fU3RvcHMuY3N2JykKICAgIHJhaWxfcmlkZXMgPC0gcmVhZC5jc3YoJ0NUQV8tX1JpZGVyc2hpcF8tX19MX19TdGF0aW9uX0VudHJpZXNfLV9EYWlseV9Ub3RhbHMuY3N2JykKfQpgYGAKCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjojMzM2NzkxIj5JbXBvcnQgU1FMIERhdGE8L3NwYW4+CgotICMjIyA8c3BhbiBzdHlsZT0iY29sb3I6IzMzNjc5MSI+Q2VudHJhbGl6ZWQgZGF0YSBhY2Nlc3MgbG9jYWxseSBvciByZW1vdGVseTwvc3Bhbj4gIyMjCiAgICAtICMjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPlJlbGF0aW9uYWwgbW9kZWwgZW5zdXJlcyBubyByZWR1bmRhbmN5PC9zcGFuPiAjIyMjCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjojMzM2NzkxIj5TZWN1cmVkIGFuZCByb2J1c3QgZGF0YSBzdG9yYWdlPC9zcGFuPiAjIyMKICAgIC0gIyMjIyA8c3BhbiBzdHlsZT0iY29sb3I6IzMzNjc5MSI+QXZhaWxhYmxlIGJhY2t1cCB3aXRoIG5vIG5lZWQgb2YgbG9jYWwgY29tcHV0ZXJzPC9zcGFuPiAjIyMjCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjojMzM2NzkxIj5UYWJsZSBhbmQgc2V0IHJlbGF0aW9ucyBzZWFtbGVzcyBpbnRlZ3JhdGVzIGludG8gZGF0YXNldHM8L3NwYW4+ICMjIwogICAgLSAjIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjojMzM2NzkxIj5hdG9taWMgY29sdW1ucyBhbmQgZGl2ZXJzZSByb3dzZXQ8L3NwYW4+ICMjIyMKCjxkaXYgY2xhc3M9ImJsdWUiPgpgYGB7cn0Kc3FsX2ltcG9ydCA8LSBmdW5jdGlvbigpIHsKICAgIGNvbm4gPC0gZGJDb25uZWN0KFJQb3N0Z3JlU1FMOjpQb3N0Z3JlU1FMKCksIGhvc3Q9IjEwLjAuMC4yMjAiLCBkYm5hbWU9ImN0YSIsCiAgICAgICAgICAgICAgICAgICAgICB1c2VyPSJjdGFkYmEiLCBwYXNzd29yZD0iY3RhMTghIiwgcG9ydD01NDMyKQoKICAgIGJ1c19tb250aCA8LSBkYkdldFF1ZXJ5KGNvbm4sICJTRUxFQ1QgKiBGUk9NIGJ1c19tb250aCIpCiAgICBidXNfcmlkZXMgPC0gZGJHZXRRdWVyeShjb25uLCAiU0VMRUNUICogRlJPTSBidXNfcmlkZXMiKQoKICAgIHJhaWxfc3RvcHMgPC0gZGJHZXRRdWVyeShjb25uLCAiU0VMRUNUICogRlJPTSByYWlsX3N0b3BzIikKICAgIHJhaWxfcmlkZXMgPC0gZGJHZXRRdWVyeShjb25uLCAiU0VMRUNUICogRlJPTSByYWlsX3JpZGVzIikKCiAgICBkYkRpc2Nvbm5lY3QoY29ubikKfQoKc3VtbWFyeShtaWNyb2JlbmNobWFyayhjc3ZfaW1wb3J0KSkKc3VtbWFyeShtaWNyb2JlbmNobWFyayhzcWxfaW1wb3J0KSkKCmBgYAo8L2Rpdj4KCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzY0NjQ2NCI+VmlldyBDU1YgRGF0YTwvc3Bhbj4KCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzY0NjQ2NCI+T2Z0ZW4gcmVxdWlyZXMgYWQtaG9jIG11bmdpbmc6IHJlLWZvcm1hdHRpbmcgb3IgY29udmVyc2lvbiBvZiBjb2x1bW5zPC9zcGFuPiAjIyMKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5DbGVhbmluZyBvdXQgcmVwb3J0cyBhbmQgbWV0YWRhdGEgZnJvbSBhY3R1YWwgZGF0YSBvbiB0aGUgZmx5PC9zcGFuPiAjIyMKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5BZGp1c3RpbmcgZGlmZmVyZW50IGxldmVscyBhbmQgcmVzaGFwaW5nIGZvciBhbmFseXRpY2FsIHB1cnBvc2VzPC9zcGFuPiAjIyMKCmBgYHtyfQpidXNfbW9udGhfY3N2IDwtIHJlYWQuY3N2KCdDVEFfLV9SaWRlcnNoaXBfLV9CdXNfUm91dGVzXy1fTW9udGhseV9EYXktVHlwZV9BdmVyYWdlc19fX1RvdGFscy5jc3YnKQpoZWFkKGJ1c19tb250aF9jc3YpCgpidXNfcmlkZXNfY3N2IDwtIHJlYWQuY3N2KCdDVEFfLV9SaWRlcnNoaXBfLV9CdXNfUm91dGVzXy1fRGFpbHlfVG90YWxzX2J5X1JvdXRlLmNzdicpCmhlYWQoYnVzX3JpZGVzX2NzdikKCnJhaWxfc3RvcHNfY3N2IDwtIHJlYWQuY3N2KCdDVEFfLV9TeXN0ZW1fSW5mb3JtYXRpb25fLV9MaXN0X29mX19MX19TdG9wcy5jc3YnKQpuYW1lcyhyYWlsX3N0b3BzX2NzdikgPC0gdG9sb3dlcihuYW1lcyhyYWlsX3N0b3BzX2NzdikpCmhlYWQocmFpbF9zdG9wc19jc3YpCgpyYWlsX3JpZGVzX2NzdiA8LSByZWFkLmNzdignQ1RBXy1fUmlkZXJzaGlwXy1fX0xfX1N0YXRpb25fRW50cmllc18tX0RhaWx5X1RvdGFscy5jc3YnKQpoZWFkKHJhaWxfcmlkZXNfY3N2KQpgYGAKCmBgYHtyfQpidXNfbW9udGhfY3N2JE1vbnRoX0JlZ2lubmluZyA8LSBhcy5EYXRlKGJ1c19tb250aF9jc3YkTW9udGhfQmVnaW5uaW5nLCBmb3JtYXQ9IiVtLyVkLyVZIiwgb3JpZ2luPSIxOTcwLTAxLTAxIikKCmJ1c19yaWRlc19jc3YkZGF0ZSA8LSBhcy5EYXRlKGJ1c19yaWRlc19jc3YkZGF0ZSwgZm9ybWF0PSIlbS8lZC8lWSIsIG9yaWdpbj0iMTk3MC0wMS0wMSIpCgpyYWlsX3JpZGVzX2NzdiRkYXRlIDwtIGFzLkRhdGUocmFpbF9yaWRlc19jc3YkZGF0ZSwgZm9ybWF0PSIlbS8lZC8lWSIsIG9yaWdpbj0iMTk3MC0wMS0wMSIpCmBgYAoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPlZpZXcgU1FMIERhdGE8L3NwYW4+CgotICMjIyA8c3BhbiBzdHlsZT0iY29sb3I6IzMzNjc5MSI+UmVsYXRpb25hbCBkYXRhYmFzZXMgYXJlIHBsYW5uZWQgc3lzdGVtczwvc3Bhbj4gIyMjCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjojMzM2NzkxIj5Db25zdHJhaW50cyBhbmQgcmVmZXJlbnRpYWwgaW50ZWdyaXR5IGVuc3VyZXMgc3RhYmlsaXR5IGFuZCByZWxhdGlvbnNoaXBzPC9zcGFuPiAjIyMKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPlF1ZXJ5IGxhbmd1YWdlIHByb3ZpZGVzIGV4cHJlc3Npb24sIHJlbGlhYmxpYmlsaXR5IGFuZCB2ZXJzYXRpbGl0eTwvc3Bhbj4gIyMjCgo8ZGl2IGNsYXNzPSJibHVlIj4KYGBge3J9CmNvbm4gPC0gZGJDb25uZWN0KFJQb3N0Z3JlU1FMOjpQb3N0Z3JlU1FMKCksIGhvc3Q9IjEwLjAuMC4yMjAiLCBkYm5hbWU9ImN0YSIsCiAgICAgICAgICAgICAgICAgIHVzZXI9ImN0YWRiYSIsIHBhc3N3b3JkPSJjdGExOCEiLCBwb3J0PTU0MzIpCgpidXNfbW9udGhfc3FsIDwtIGRiR2V0UXVlcnkoY29ubiwgIlNFTEVDVCAqIEZST00gYnVzX21vbnRoIikKaGVhZChidXNfbW9udGhfc3FsKQoKYnVzX3JpZGVzX3NxbCA8LSBkYkdldFF1ZXJ5KGNvbm4sICJTRUxFQ1QgKiBGUk9NIGJ1c19yaWRlcyIpCmhlYWQoYnVzX3JpZGVzX3NxbCkKCnJhaWxfc3RhdGlvbnNfc3FsIDwtIGRiR2V0UXVlcnkoY29ubiwgIlNFTEVDVCAqIEZST00gcmFpbF9zdGF0aW9ucyIpCmhlYWQocmFpbF9zdGF0aW9uc19zcWwpCgpyYWlsX3JpZGVzX3NxbCA8LSBkYkdldFF1ZXJ5KGNvbm4sICJTRUxFQ1QgKiBGUk9NIHJhaWxfcmlkZXMiKQpoZWFkKHJhaWxfcmlkZXNfc3FsKQoKYGBgCjwvZGl2PgoKCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzY0NjQ2NCI+QWdncmVnYXRlIENTViBEYXRhPC9zcGFuPiAKCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzY0NjQ2NCI+RGF0YSB0b29sIHN5bnRheCBjYW4gYmUgZm9ybWlkYWJsZSBmb3IgbmV3Y29tZXJzIG9yIHJldHVybmluZyB1c2Vyczwvc3Bhbj4gIyMjCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzY0NjQ2NCI+Q29tcGxleCBwcm9jZXNzZXMgcmVxdWlyZSBsb25nIHBpcGluZy9jaGFpbmluZyBvZiBvYmplY3RzIGFuZCBtZXRob2RzPC9zcGFuPiAjIyMKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5MYW5ndWFnZSBsYWNrcyBzZXQtYmFzZWQgKGkuZS4sIHJlbGF0aW9ucywgam9pbiwgdW5pb24pIGZyYW1ld29yazwvc3Bhbj4gIyMjCiAgCgpgYGB7cn0KIyBNRVJHRQphZ2dfY3N2IDwtIG1lcmdlKHVuaXF1ZShidXNfbW9udGhfY3N2W2MoInJvdXRlIiwgInJvdXRlbmFtZSIpXSksIGJ1c19yaWRlc19jc3YsIGJ5PSJyb3V0ZSIpCgojIEFHR1JFR0FURQphZ2dfY3N2IDwtIGRvLmNhbGwoZGF0YS5mcmFtZSwgCiAgICAgICAgICAgICAgICAgICBhZ2dyZWdhdGUocmlkZXMgfiByb3V0ZSArIHJvdXRlbmFtZSwgYWdnX2NzdiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oeCkgYyhjb3VudD1sZW5ndGgoeCksIHN1bT1zdW0oeCksIG1lYW49bWVhbih4KSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZWRpYW49bWVkaWFuKHgpLCBtaW49bWluKHgpLCBtYXg9bWF4KHgpKSkpCiMgT1JERVIKYWdnX2NzdiA8LSB3aXRoKGFnZ19jc3YsIGFnZ19jc3Zbb3JkZXIoLXJpZGVzLnN1bSksXSkKCmFnZ19jc3YKYGBgCgoKIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzY0NjQ2NCI+VG9wIDUgQnVzIFJvdXRlczwvc3Bhbj4gCgpgYGB7cn0KIyBNRVJHRQphZ2dfY3N2IDwtIG1lcmdlKHN1YnNldCh1bmlxdWUoYnVzX21vbnRoX2NzdltjKCJyb3V0ZSIsICJyb3V0ZW5hbWUiKV0pLCAKICAgICAgICAgICAgICAgICAgICAgICAgcm91dGVuYW1lICVpbiUgYygiNzl0aCIsICJBc2hsYW5kIiwgIkNoaWNhZ28iLCAiV2VzdGVybiIsICJDb3R0YWdlIEdyb3ZlIikpLAogICAgICAgICAgICAgICAgIHRyYW5zZm9ybShidXNfcmlkZXNfY3N2LCB5ZWFyPWZvcm1hdChkYXRlLCAiJVkiKSksCiAgICAgICAgICAgICAgICAgYnk9InJvdXRlIikKCiMgQUdHUkVHQVRFCmFnZ19jc3YgPC0gZG8uY2FsbChkYXRhLmZyYW1lLCAKICAgICAgICAgICAgICAgICAgIGFnZ3JlZ2F0ZShyaWRlcyB+IHJvdXRlbmFtZSArIHllYXIsIGFnZ19jc3YsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uKHgpIGMoY291bnQ9bGVuZ3RoKHgpLCBzdW09c3VtKHgpLCBtZWFuPW1lYW4oeCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWVkaWFuPW1lZGlhbih4KSwgbWluPW1pbih4KSwgbWF4PW1heCh4KSkpKQojIE9SREVSCmFnZ19jc3YgPC0gd2l0aChhZ2dfY3N2LCBhZ2dfY3N2W29yZGVyKHJvdXRlbmFtZSwgeWVhciksXSkKCmFnZ19jc3YKYGBgCgoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5HcmFwaCBDU1YgRGF0YTwvc3Bhbj4gCgpgYGB7ciBmaWcxLCBmaWcuaGVpZ2h0ID0gNCwgZmlnLndpZHRoID0gMTAsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQoKc2VhYm9yblBhbGV0dGUgPC0gYygiIzRjNzJiMCIsIiM1NWE4NjgiLCIjYzQ0ZTUyIiwiIzgxNzJiMiIsIiNjY2I5NzQiLCIjNjRiNWNkIiwiIzRjNzJiMCIsIiM1NWE4NjgiLAogICAgICAgICAgICAgICAgICAgICIjYzQ0ZTUyIiwiIzgxNzJiMiIsIiNjY2I5NzQiLCIjNjRiNWNkIiwiIzRjNzJiMCIsIiM1NWE4NjgiLCIjYzQ0ZTUyIiwiIzgxNzJiMiIsCiAgICAgICAgICAgICAgICAgICAgIiNjY2I5NzQiLCIjNjRiNWNkIiwiIzRjNzJiMCIsIiM1NWE4NjgiLCIjYzQ0ZTUyIiwiIzgxNzJiMiIsIiNjY2I5NzQiLCIjNjRiNWNkIiwKICAgICAgICAgICAgICAgICAgICAiIzRjNzJiMCIsIiM1NWE4NjgiLCIjYzQ0ZTUyIiwiIzgxNzJiMiIsIiNjY2I5NzQiLCIjNjRiNWNkIiwiIzRjNzJiMCIsIiM1NWE4NjgiLAogICAgICAgICAgICAgICAgICAgICIjYzQ0ZTUyIiwiIzgxNzJiMiIsIiNjY2I5NzQiLCIjNjRiNWNkIiwiIzRjNzJiMCIsIiM1NWE4NjgiLCIjYzQ0ZTUyIiwiIzgxNzJiMiIsCiAgICAgICAgICAgICAgICAgICAgIiNjY2I5NzQiLCIjNjRiNWNkIiwiIzRjNzJiMCIsIiM1NWE4NjgiLCIjYzQ0ZTUyIiwiIzgxNzJiMiIsIiNjY2I5NzQiLCIjNjRiNWNkIiwKICAgICAgICAgICAgICAgICAgICAiIzRjNzJiMCIsIiM1NWE4NjgiLCIjYzQ0ZTUyIiwiIzgxNzJiMiIsIiNjY2I5NzQiLCIjNjRiNWNkIiwiIzRjNzJiMCIsIiM1NWE4NjgiLAogICAgICAgICAgICAgICAgICAgICIjYzQ0ZTUyIiwiIzgxNzJiMiIsIiNjY2I5NzQiLCIjNjRiNWNkIikKCmdncGxvdChhZ2dfY3N2LCBhZXMoeWVhciwgcmlkZXMuc3VtLCBmaWxsPXJvdXRlbmFtZSkpICsgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgbGFicyh0aXRsZT0iVG9wIDUgQ1RBICdMJyBCdXMgUm91dGVzIiwgeD0iWWVhciIsIHk9IlJpZGVzIikgKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBsYWJlbD1jb21tYSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHNlYWJvcm5QYWxldHRlKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3Q9MC41LCBzaXplPTE4KSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT0wLCBoanVzdD0wLjUpKQpgYGAKCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjojMzM2NzkxIj5BZ2dyZWdhdGUgU1FMIERhdGE8L3NwYW4+IAoKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPkNsZWFyLCBjb21wYWN0IGRlY2xhcmF0aXZlIGxhbmd1YWdlIHdpdGggcG9ydGFiaWxpdHk8L3NwYW4+ICMjIyMKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPlByb2Nlc3Npbmcgd2l0aCB2aXJ0dWFsIHRhYmxlcyBvY2N1cnMgYmVoaW5kIHRoZSBzY2VuZTwvc3Bhbj4gIyMjCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjojMzM2NzkxIj5TZXQtYmFzZWQgZnJhbWV3b3JrIGZhY2lsaXRhdGVzIGJsb2Nrd2lzZSwgdmVjdG9yaXplZCBwcm9jZXNzPC9zcGFuPiAjIyMKCjxkaXYgY2xhc3M9ImJsdWUiPgpgYGB7cn0Kc3FsIDwtICdTRUxFQ1QgcnQucm91dGVfbmFtZSwgQ09VTlQocmQucmlkZXMpIEFTICJjb3VudCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTVU0ocmQucmlkZXMpIEFTICJzdW0iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQVZHKHJkLnJpZGVzKSBBUyAibWVhbiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNRURJQU4ocmQucmlkZXMpIEFTICJtZWRpYW4iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNSU4ocmQucmlkZXMpIEFTICJtaW4iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTUFYKHJkLnJpZGVzKSBBUyAibWF4IgogICAgICAgIEZST00gYnVzX3JvdXRlcyBydAogICAgICAgIElOTkVSIEpPSU4gYnVzX3JpZGVzIHJkIE9OIHJ0LnJvdXRlX2lkID0gcmQucm91dGVfaWQKICAgICAgICBHUk9VUCBCWSBydC5yb3V0ZV9uYW1lCiAgICAgICAgT1JERVIgQlkgU1VNKHJkLnJpZGVzKSBERVNDJwoKYWdnX3NxbCA8LSBkYkdldFF1ZXJ5KGNvbm4sIHNxbCkKCmFnZ19zcWwKYGBgCgpgYGB7cn0Kc3FsIDwtICdTRUxFQ1QgcnQucm91dGVfbmFtZSwgREFURV9QQVJUKFwneWVhclwnLCByZC5yaWRlX2RhdGUpOjppbnRlZ2VyIEFTICJ5ZWFyIiwgCiAgICAgICAgICAgICBDT1VOVChyZC5yaWRlcykgQVMgImNvdW50IiwgCiAgICAgICAgICAgICBTVU0ocmQucmlkZXMpIEFTICJzdW0iLCAKICAgICAgICAgICAgIEFWRyhyZC5yaWRlcykgQVMgIm1lYW4iLCAKICAgICAgICAgICAgIE1FRElBTihyZC5yaWRlcykgQVMgIm1lZGlhbiIsCiAgICAgICAgICAgICBNSU4ocmQucmlkZXMpIEFTICJtaW4iLCAKICAgICAgICAgICAgIE1BWChyZC5yaWRlcykgQVMgIm1heCIKICAgICAgRlJPTSBidXNfcm91dGVzIHJ0CiAgICAgIElOTkVSIEpPSU4gYnVzX3JpZGVzIHJkIE9OIHJ0LnJvdXRlX2lkID0gcmQucm91dGVfaWQKICAgICAgV0hFUkUgcnQucm91dGVfbmFtZSBJTiAoXCc3OXRoXCcsIFwnQXNobGFuZFwnLCBcJ0NoaWNhZ29cJywgXCdXZXN0ZXJuXCcsIFwnQ290dGFnZSBHcm92ZVwnKQogICAgICBHUk9VUCBCWSBydC5yb3V0ZV9uYW1lLCBEQVRFX1BBUlQoXCd5ZWFyXCcsIHJkLnJpZGVfZGF0ZSk6OmludGVnZXIKICAgICAgT1JERVIgQlkgcnQucm91dGVfbmFtZSwgREFURV9QQVJUKFwneWVhclwnLCByZC5yaWRlX2RhdGUpOjppbnRlZ2VyJwoKYWdnX3NxbCA8LSBkYkdldFF1ZXJ5KGNvbm4sIHNxbCkKCmFnZ19zcWwKYGBgCjwvZGl2PgoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPkdyYXBoIFNRTCBEYXRhPC9zcGFuPgoKPGRpdiBjbGFzcz0iYmx1ZSI+CmBgYHtyIGZpZzIsIGZpZy5oZWlnaHQgPSA0LCBmaWcud2lkdGggPSAxMCwgZmlnLmFsaWduID0gImNlbnRlciJ9CmdncGxvdChhZ2dfc3FsLCBhZXMoeWVhciwgc3VtLCBjb2xvcj1yb3V0ZV9uYW1lKSkgKyAKICBnZW9tX2xpbmUoc3RhdD0iaWRlbnRpdHkiKSArIGdlb21fcG9pbnQoc3RhdD0iaWRlbnRpdHkiKSArCiAgbGFicyh0aXRsZT0iVG9wIDUgQ1RBICdMJyBCdXMgUm91dGVzIiwgeD0iWWVhciIsIHk9IlJpZGVzIikgKwogIHNjYWxlX3hfY29udGludW91cygieWVhciIsIGJyZWFrcz11bmlxdWUoYWdnX3NxbCR5ZWFyKSkgKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBsYWJlbD1jb21tYSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBzZWFib3JuUGFsZXR0ZSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0iYm90dG9tIiwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0PTAuNSwgc2l6ZT0xOCksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9MCwgaGp1c3Q9MC41KSkKYGBgCjwvZGl2PgoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5DU1YgRGF0YSBEaWFnbm9zdGljczwvc3Bhbj4gIyMKCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzY0NjQ2NCI+SW1wZXJhdGl2ZSBuYXR1cmUgb2YgcHJvY2Vzc2luZzwvc3Bhbj4gIyMjCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzY0NjQ2NCI+RGVuc2UsIG5lc3RlZCBjYWxscyBmb3IgbGF5ZXJlZCBzdGVwczwvc3Bhbj4gIyMjCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzY0NjQ2NCI+TGltaXRlZCB0byBhcHBsaWNhdGlvbiBsYXllcjwvc3Bhbj4gIyMjCgpgYGB7cn0KIyBUUkFOU0ZPUk0KYWdnX2NzdiA8LSB0cmFuc2Zvcm0ocmFpbF9yaWRlc19jc3YsIHllYXI9Zm9ybWF0KGRhdGUsICIlWSIpKQoKIyBBR0dSRUdBVEUKYWdnX2NzdiA8LSBkby5jYWxsKGRhdGEuZnJhbWUsIAogICAgICAgICAgICAgICAgICAgYWdncmVnYXRlKHJpZGVzIH4gc3RhdGlvbl9pZCArIHN0YXRpb25uYW1lICsgeWVhciwgYWdnX2NzdiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oeCkgYyhjb3VudD1sZW5ndGgoeCksIHN1bT1zdW0oeCksIG1lYW49bWVhbih4KSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZWRpYW49bWVkaWFuKHgpLCBtaW49bWluKHgpLCBtYXg9bWF4KHgpKSkpCiMgT1JERVIKYWdnX2NzdiA8LSB3aXRoKGFnZ19jc3YsIGFnZ19jc3Zbb3JkZXIoLXJpZGVzLnN1bSksXSkKCmFnZ19jc3YKYGBgCgoKYGBge3J9CiMgUkVTSEFQRQpyYWlsX2xpbmVzIDwtIGMoInJlZCIsICJibHVlIiwgImdyZWVuIiwgImJyb3duIiwgInB1cnBsZSIsICJwdXJwbGVfZXhwIiwgInllbGxvdyIsICJwaW5rIiwgIm9yYW5nZSIpCgpyYWlsX2xvbmcgPC0gcmVzaGFwZShzZXROYW1lcyhyYWlsX3N0b3BzX2NzdltjKCJtYXBfaWQiLCAic3RhdGlvbl9uYW1lIiwgImxvY2F0aW9uIiwgInJlZCIsICJibHVlIiwgImciLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYnJuIiwgInAiLCAicGV4cCIsICJ5IiwgInBuayIsICJvIildLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjKCJzdGF0aW9uX2lkIiwgInN0YXRpb25fbmFtZSIsICJsb2NhdGlvbiIsIHJhaWxfbGluZXMpKSwKICAgICAgICAgICAgICAgICAgICAgdmFyeWluZyA9IHJhaWxfbGluZXMsIHYubmFtZXMgPSAidmFsdWUiLCAKICAgICAgICAgICAgICAgICAgICAgdGltZXZhciA9ICJyYWlsX2xpbmUiLCB0aW1lcyA9IHJhaWxfbGluZXMsCiAgICAgICAgICAgICAgICAgICAgIG5ldy5yb3cubmFtZXMgPSAxOjEwMDAwLCBkaXJlY3Rpb24gPSAibG9uZyIpCgojIFNVQlNFVApyYWlsX2xvbmcgPC0gdW5pcXVlKHN1YnNldChyYWlsX2xvbmcsIHZhbHVlPT0ndHJ1ZScpW2MoInN0YXRpb25faWQiLCAic3RhdGlvbl9uYW1lIiwgImxvY2F0aW9uIiwgInJhaWxfbGluZSIpXSkKCiMgT1JERVIKcmFpbF9sb25nIDwtIHdpdGgocmFpbF9sb25nLCByYWlsX2xvbmdbb3JkZXIoc3RhdGlvbl9pZCwgcmFpbF9saW5lKSxdKQpyb3cubmFtZXMocmFpbF9sb25nKSA8LSBOVUxMCgpyYWlsX2xvbmcKYGBgCgoKYGBge3J9Cm1lcmdlX3JhaWwgPC0gbWVyZ2UoYWdnX2NzdiwgcmFpbF9sb25nLCBieT0ic3RhdGlvbl9pZCIpCgptZXJnZV9yYWlsJHJpZGVzLnRvdGFsIDwtIG1lcmdlX3JhaWwkcmlkZXMuc3VtIC8gd2l0aChtZXJnZV9yYWlsLCBhdmUoc3RhdGlvbl9pZCwgc3RhdGlvbl9pZCwgeWVhciwgRlVOPWxlbmd0aCkpCgptZXJnZV9yYWlsCmBgYAoKCmBgYHtyfQphZ2dfY3N2IDwtIGFnZ3JlZ2F0ZShyaWRlcy50b3RhbCB+IHllYXIgKyByYWlsX2xpbmUsIG1lcmdlX3JhaWwsIHN1bSkKYWdnX2NzdiA8LSB3aXRoKGFnZ19jc3YsIGFnZ19jc3Zbb3JkZXIocmFpbF9saW5lLCB5ZWFyKSxdKQpyb3cubmFtZXMoYWdnX2NzdikgPC0gTlVMTAoKYWdnX2NzdgpgYGAKCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjojMzM2NzkxIj5TUUwgRGF0YSBEaWFnbm9zdGljczwvc3Bhbj4KCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjojMzM2NzkxIj5Db21wbGV4IHByb2Nlc3Npbmcgc3RpbGwgcmVhZGFibGUgYW5kIG1haW50YWluYWJsZTwvc3Bhbj4gIyMjCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjojMzM2NzkxIj5DVEVzIGNsZWFybHkgc2hvdyB1bmRlcmx5aW5nIHRhYmxlcyBhbmQgdmlld3Mgd2l0aG91dCBoZWxwZXIgb2JqZWN0czwvc3Bhbj4gIyMjCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjojMzM2NzkxIj5XaW5kb3cgZnVuY3Rpb25zIGFsbG93IHVzZWZ1bCBpbmxpbmUgY2FsY3VsYXRpb25zPC9zcGFuPiAjIyMKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPkNvbmRpdGlvbmFsIGFnZ3JlZ2F0aW9ucyBjbGVhcmx5IHNob3cgcmVzaGFwZWQgZGF0YTwvc3Bhbj4gIyMjCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjojMzM2NzkxIj5TZWFtbGVzcyBjYXN0aW5nIGFuZCBjb252ZXJzaW9uIG9mIHR5cGVzIHdpdGggYDo6YCBvcGVyYXRvcjwvc3Bhbj4gIyMjCgo8ZGl2IGNsYXNzPSJibHVlIj4KYGBge3J9CnNxbCA8LSAnV0lUSCBzdGF0aW9uX2FnZyBBUwogICAgICAgICAoU0VMRUNUIERBVEVfUEFSVChcJ3llYXJcJywgci5yaWRlX2RhdGUpOjppbnRlZ2VyIEFTICJ5ZWFyIiwKICAgICAgICAgICAgICAgICByLnN0YXRpb25faWQsCiAgICAgICAgICAgICAgICAgci5zdGF0aW9uX25hbWUsCiAgICAgICAgICAgICAgICAgQ09VTlQoci5yaWRlcyk6Om51bWVyaWMoMjAsNSkgQVMgImNvdW50IiwgCiAgICAgICAgICAgICAgICAgU1VNKHIucmlkZXMpOjpudW1lcmljKDIwLDUpIEFTICJzdW0iLCAKICAgICAgICAgICAgICAgICBBVkcoci5yaWRlcyk6Om51bWVyaWMoMjAsNSkgQVMgIm1lYW4iLCAKICAgICAgICAgICAgICAgICBNRURJQU4oci5yaWRlcyk6Om51bWVyaWMoMjAsNSkgQVMgIm1lZGlhbiIsCiAgICAgICAgICAgICAgICAgTUlOKHIucmlkZXMpOjpudW1lcmljKDIwLDUpIEFTICJtaW4iLCAKICAgICAgICAgICAgICAgICBNQVgoci5yaWRlcyk6Om51bWVyaWMoMjAsNSkgQVMgIm1heCIKICAgICAgICAgIEZST00gcmFpbF9yaWRlcyByCiAgICAgICAgICBHUk9VUCBCWSBEQVRFX1BBUlQoXCd5ZWFyXCcsIHIucmlkZV9kYXRlKSwKICAgICAgICAgICAgICAgICAgIHIuc3RhdGlvbl9pZCwKICAgICAgICAgICAgICAgICAgIHIuc3RhdGlvbl9uYW1lCiAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgCiAgICAgIG1lcmdlX3JhaWwgQVMKICAgICAgICAgKFNFTEVDVCBzLiosIAogICAgICAgICAgICAgICAgIHIucmFpbF9saW5lLAogICAgICAgICAgICAgICAgIChzLiJzdW0iIC8gQ09VTlQoKikgT1ZFUihQQVJUSVRJT04gQlkgcy5zdGF0aW9uX2lkLCAieWVhciIpKSBBUyByYWlsX3RvdGFsCiAgICAgICAgICBGUk9NIHN0YXRpb25fYWdnIHMKICAgICAgICAgIElOTkVSIEpPSU4gcmFpbF9zdGF0aW9ucyByIE9OIHMuc3RhdGlvbl9pZCA9IHIuc3RhdGlvbl9pZAogICAgICAgICApCiAgICAgICAgIAogICAgICBTRUxFQ1QgbS4ieWVhciIsIG0ucmFpbF9saW5lLCBTVU0obS5yYWlsX3RvdGFsKSAgQVMgcmFpbF90b3RhbAogICAgICBGUk9NIG1lcmdlX3JhaWwgbQogICAgICBHUk9VUCBCWSBtLiJ5ZWFyIiwgbS5yYWlsX2xpbmUKICAgICAgT1JERVIgQlkgbS5yYWlsX2xpbmUsIG0uInllYXIiJwogIAphZ2dfc3FsIDwtIGRiR2V0UXVlcnkoY29ubiwgc3FsKQoKYWdnX3NxbApgYGAKCgpgYGB7ciBmaWczLCBmaWcuaGVpZ2h0ID0gNCwgZmlnLndpZHRoID0gMTAsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQpjdGFfcGFsZXR0ZSA8LSBjKGJsdWU9IiMwMEExREUiLCBicm93bj0iIzYyMzYxQiIsIGdyZWVuPSIjMDA5QjNBIiwgb3JhbmdlPSIjRjk0NjFDIiwgcGluaz0iI0UyN0VBNiIsCiAgICAgICAgICAgICAgICAgcHVycGxlPSIjNTIyMzk4IiwgcHVycGxlX2V4cD0iIzgwNTlCQSIsIHJlZD0iI0M2MEMzMCIsIHllbGxvdz0iI0Y5RTMwMCIpCgpnZ3Bsb3Qoc3Vic2V0KGFnZ19zcWwsIHllYXIgPiAyMDEyKSwgYWVzKHllYXIsIHJhaWxfdG90YWwsIGZpbGw9cmFpbF9saW5lKSkgKyBnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIpICsKICBsYWJzKHRpdGxlPSJDVEEgU3lzdGVtICdMJyBMaW5lcyAtIFRvdGFsIFJpZGVzIEJ5IFllYXIiLCB4PSJZZWFyIiwgeT0iUmlkZXMiKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSwgInllYXIiLCBicmVha3M9dW5pcXVlKGFnZ19zcWwkeWVhcikpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgbGFiZWw9Y29tbWEpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjdGFfcGFsZXR0ZSkgKyBndWlkZXMoZmlsbD1ndWlkZV9sZWdlbmQoIlJhaWwgTGluZXMiLCBucm93PTEpKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3Q9MC41LCBzaXplPTE4KSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT0wLCBoanVzdD0wLjUpKQpgYGAKCjwvZGl2PgoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTE7Ij5EaXN0cmlidXRpb248L3NwYW4KCmBgYHtyfQpzcWwgPC0gJ1NFTEVDVCByLnN0YXRpb25faWQsIHIucmlkZV9kYXRlLCByLnN0YXRpb25fbmFtZSwgcy5yYWlsX2xpbmUsIHIucmlkZXMsCiAgICAgICAgICAgICAgIChyLnJpZGVzIC8gQ09VTlQoKikgT1ZFUihQQVJUSVRJT04gQlkgcy5zdGF0aW9uX2lkLCByLnJpZGVfZGF0ZSkpIEFTIHJpZGVzX3RvdGFsCiAgICAgICAgRlJPTSByYWlsX3JpZGVzIHIKICAgICAgICBJTk5FUiBKT0lOIHJhaWxfc3RhdGlvbnMgcyBPTiBzLnN0YXRpb25faWQgPSByLnN0YXRpb25faWQnCgpoaXN0X2xvbmcgPC0gZGJHZXRRdWVyeShjb25uLCBzcWwpCgpoaXN0X2xvbmcKYGBgCgpgYGB7ciBmaWc0LCBmaWcuaGVpZ2h0ID0gNCwgZmlnLndpZHRoID0gMTAsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQpnZ3Bsb3QoaGlzdF9sb25nLCBhZXMoeD1yaWRlc190b3RhbCwgZmlsbD1yYWlsX2xpbmUpKSArCiAgIGdlb21faGlzdG9ncmFtKGRhdGE9c3Vic2V0KGhpc3RfbG9uZywgcmFpbF9saW5lID09ICdyZWQnKSwgYmlucz0xMDApICsKICAgZ2VvbV9oaXN0b2dyYW0oZGF0YT1zdWJzZXQoaGlzdF9sb25nLCByYWlsX2xpbmUgPT0gJ2JsdWUnKSwgYmlucz0xMDApICsKICAgZ2VvbV9oaXN0b2dyYW0oZGF0YT1zdWJzZXQoaGlzdF9sb25nLCByYWlsX2xpbmUgPT0gJ2Jyb3duJyksIGJpbnM9MTAwKSArCiAgIGdlb21faGlzdG9ncmFtKGRhdGE9c3Vic2V0KGhpc3RfbG9uZywgcmFpbF9saW5lID09ICdncmVlbicpLCBiaW5zPTEwMCkgKwogICBnZW9tX2hpc3RvZ3JhbShkYXRhPXN1YnNldChoaXN0X2xvbmcsIHJhaWxfbGluZSA9PSAnb3JhbmdlJyksIGJpbnM9MTAwKSArCiAgIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApKSArCiAgIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBsaW09YygwLDQwMDAwKSwgbGFiZWw9Y29tbWEpICsKICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYyhyZWQgPSAiI0M2MEMzMCIsIGJsdWUgPSAiIzAwQTFERSIsIGJyb3duID0gIiM2MjM2MUIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyZWVuID0gIiMwMDlCM0EiLCBvcmFuZ2UgPSAiI0Y5NDYxQyIpKSArCiAgIGxhYnModGl0bGU9IkNUQSBSaWRlcnNoaXAgRGlzdHJpYnV0aW9uIEJ5IFJhaWwgTGluZSIsIGZpbGw9IlJhaWwgTGluZSIpICsKICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdD0wLjUsIHNpemU9MTgpKQpgYGAKCmBgYHtyIGZpZzUsIGZpZy5oZWlnaHQgPSA0LCBmaWcud2lkdGggPSAxMCwgZmlnLmFsaWduID0gImNlbnRlciJ9CmdncGxvdCh0cmFuc2Zvcm0oaGlzdF9sb25nLCB5ZWFyID0gZm9ybWF0KHJpZGVfZGF0ZSwgJyVZJykpLCAKICAgICAgIGFlcyh4PXllYXIsIHk9cmlkZXNfdG90YWwsIGZpbGw9eWVhcikpICsgCiAgICBnZW9tX2JveHBsb3QoKSArIGd1aWRlcyhmaWxsPUZBTFNFKSArCiAgICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgbGltPWMoMCw0MDAwMCksIGxhYmVsPWNvbW1hKSArCiAgICBsYWJzKHRpdGxlPSJDVEEgUmlkZXJzaGlwIEJveHBsb3QgQnkgWWVhciIpICsKICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3Q9MC41LCBzaXplPTE4KSkKYGBgCgpgYGB7ciBmaWc2LCBmaWcuaGVpZ2h0ID0gNCwgZmlnLndpZHRoID0gMTAsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQpnZ3Bsb3QoaGlzdF9sb25nLCBhZXMoeD1yYWlsX2xpbmUsIHk9cmlkZXNfdG90YWwsIGZpbGw9cmFpbF9saW5lKSkgKyAKICAgIGdlb21fYm94cGxvdCgpICsgZ3VpZGVzKGZpbGw9RkFMU0UpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGN0YV9wYWxldHRlKSArCiAgICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgbGltPWMoMCw0MDAwMCksIGxhYmVsPWNvbW1hKSArCiAgICBsYWJzKHRpdGxlPSJDVEEgUmlkZXJzaGlwIEJveHBsb3QgQnkgUmFpbCBMaW5lIikgKwogICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdD0wLjUsIHNpemU9MTgpKQpgYGAKCmBgYHtyIGZpZzcsIGZpZy5oZWlnaHQgPSA0LCBmaWcud2lkdGggPSAxMCwgZmlnLmFsaWduID0gImNlbnRlciJ9CmdncGxvdChzdWJzZXQod2l0aGluKGhpc3RfbG9uZywgeyB5ZWFyIDwtIGZvcm1hdChyaWRlX2RhdGUsICclWScpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ZWFyIDwtIGFzLmludGVnZXIoYXMuY2hhcmFjdGVyKHllYXIpKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0pLAogICAgICAgICAgICAgIHllYXIgPj0gMjAxNSAmIHllYXIgPD0gMjAxNyksIAogICAgICAgYWVzKHg9ZmFjdG9yKHllYXIpLCB5PXJpZGVzX3RvdGFsLCBmaWxsPXJhaWxfbGluZSkpICsgCiAgICBnZW9tX2JveHBsb3QoKSArIGd1aWRlcyhmaWxsPWd1aWRlX2xlZ2VuZCgiUmFpbCBMaW5lcyIsIG5yb3c9MSkpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGN0YV9wYWxldHRlKSArCiAgICBzY2FsZV94X2Rpc2NyZXRlKGV4cGFuZCA9IGMoMCwgMCkpICsKICAgIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBsaW09YygwLDQwMDAwKSwgbGFiZWw9Y29tbWEpICsKICAgIGxhYnModGl0bGU9IkNUQSBSaWRlcnNoaXAgQm94cGxvdCBCeSBSYWlsIExpbmUsIDIwMTUtMjAxNyIsIHg9IlllYXIiLCB5PSJSaWRlcyIpICsKICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0iYm90dG9tIiwgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdD0wLjUsIHNpemU9MTgpKQpgYGAKCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IzMzNjc5MSI+Q29ycmVsYXRpb248L3NwYW4+Cgo8ZGl2IGNsYXNzPSJibHVlIj4KYGBge3J9CnNxbCA8LSAnV0lUSCBzdGF0aW9uX2FnZyBBUwogICAgICAgICAoU0VMRUNUIERBVEVfUEFSVChcJ3llYXJcJywgci5yaWRlX2RhdGUpOjppbnRlZ2VyIEFTICJ5ZWFyIiwKICAgICAgICAgICAgICAgICByLnN0YXRpb25faWQsCiAgICAgICAgICAgICAgICAgci5zdGF0aW9uX25hbWUsCiAgICAgICAgICAgICAgICAgQ09VTlQoci5yaWRlcyk6Om51bWVyaWMoMjAsNSkgQVMgImNvdW50IiwgCiAgICAgICAgICAgICAgICAgU1VNKHIucmlkZXMpOjpudW1lcmljKDIwLDUpIEFTICJzdW0iLCAKICAgICAgICAgICAgICAgICBBVkcoci5yaWRlcyk6Om51bWVyaWMoMjAsNSkgQVMgIm1lYW4iLCAKICAgICAgICAgICAgICAgICBNRURJQU4oci5yaWRlcyk6Om51bWVyaWMoMjAsNSkgQVMgIm1lZGlhbiIsCiAgICAgICAgICAgICAgICAgTUlOKHIucmlkZXMpOjpudW1lcmljKDIwLDUpIEFTICJtaW4iLCAKICAgICAgICAgICAgICAgICBNQVgoci5yaWRlcyk6Om51bWVyaWMoMjAsNSkgQVMgIm1heCIKICAgICAgICAgIEZST00gcmFpbF9yaWRlcyByCiAgICAgICAgICBHUk9VUCBCWSBEQVRFX1BBUlQoXCd5ZWFyXCcsIHIucmlkZV9kYXRlKSwKICAgICAgICAgICAgICAgICAgIHIuc3RhdGlvbl9pZCwKICAgICAgICAgICAgICAgICAgIHIuc3RhdGlvbl9uYW1lCiAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgCiAgICAgIG1lcmdlX3JhaWwgQVMKICAgICAgICAgKFNFTEVDVCBzLiosIAogICAgICAgICAgICAgICAgIHIucmFpbF9saW5lLAogICAgICAgICAgICAgICAgIChzLiJzdW0iIC8gQ09VTlQoKikgT1ZFUihQQVJUSVRJT04gQlkgcy5zdGF0aW9uX2lkLCAieWVhciIpKSBBUyByYWlsX3RvdGFsCiAgICAgICAgICBGUk9NIHN0YXRpb25fYWdnIHMKICAgICAgICAgIElOTkVSIEpPSU4gcmFpbF9zdGF0aW9ucyByIE9OIHMuc3RhdGlvbl9pZCA9IHIuc3RhdGlvbl9pZAogICAgICAgICApCiAgICAgICAgIAogICAgICBTRUxFQ1QgbS4ieWVhciIsCiAgICAgICAgICAgICBTVU0ocmFpbF90b3RhbCkgRklMVEVSIChXSEVSRSByYWlsX2xpbmUgPSBcJ2JsdWVcJykgQVMgYmx1ZSwKICAgICAgICAgICAgIFNVTShyYWlsX3RvdGFsKSBGSUxURVIgKFdIRVJFIHJhaWxfbGluZSA9IFwnYnJvd25cJykgQVMgYnJvd24sCiAgICAgICAgICAgICBTVU0ocmFpbF90b3RhbCkgRklMVEVSIChXSEVSRSByYWlsX2xpbmUgPSBcJ2dyZWVuXCcpIEFTIGdyZWVuLAogICAgICAgICAgICAgU1VNKHJhaWxfdG90YWwpIEZJTFRFUiAoV0hFUkUgcmFpbF9saW5lID0gXCdvcmFuZ2VcJykgQVMgb3JhbmdlLAogICAgICAgICAgICAgU1VNKHJhaWxfdG90YWwpIEZJTFRFUiAoV0hFUkUgcmFpbF9saW5lID0gXCdwaW5rXCcpIEFTIHBpbmssCiAgICAgICAgICAgICBTVU0ocmFpbF90b3RhbCkgRklMVEVSIChXSEVSRSByYWlsX2xpbmUgPSBcJ3B1cnBsZVwnKSBBUyBwdXJwbGUsCiAgICAgICAgICAgICBTVU0ocmFpbF90b3RhbCkgRklMVEVSIChXSEVSRSByYWlsX2xpbmUgPSBcJ3B1cnBsZV9leHBcJykgQVMgcHVycGxlX2V4cCwKICAgICAgICAgICAgIFNVTShyYWlsX3RvdGFsKSBGSUxURVIgKFdIRVJFIHJhaWxfbGluZSA9IFwncmVkXCcpIEFTIHJlZCwKICAgICAgICAgICAgIFNVTShyYWlsX3RvdGFsKSBGSUxURVIgKFdIRVJFIHJhaWxfbGluZSA9IFwneWVsbG93XCcpIEFTIHllbGxvdwogICAgICBGUk9NIG1lcmdlX3JhaWwgbQogICAgICBHUk9VUCBCWSBtLiJ5ZWFyIgogICAgICBPUkRFUiBCWSBtLiJ5ZWFyIicKCndpZGVfc3FsIDwtIGRiR2V0UXVlcnkoY29ubiwgc3FsKQoKd2lkZV9zcWwKYGBgCjwvZGl2PgoKPGRpdiBjbGFzcz0iYmx1ZSI+CmBgYHtyfQpjb3Iod2lkZV9zcWxbLTFdLCB1c2UgPSAiY29tcGxldGUub2JzIiwgbWV0aG9kPSJwZWFyc29uIikKYGBgCjwvZGl2PgoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPlQtdGVzdHM8L3NwYW4+Cgo8ZGl2IGNsYXNzPSJibHVlIj4KYGBge3J9CmNvbWJucyA8LSBhcy5saXN0KG91dGVyKHJhaWxfbGluZXMsIHJhaWxfbGluZXMsIGZ1bmN0aW9uKHgsIHkpIGlmZWxzZSh4PT15LCBOQSwgcGFzdGUoeCwgeSkpKSkKY29tYm5zIDwtIGxhcHBseShjb21ibnNbIWlzLm5hKGNvbWJucyldLCBmdW5jdGlvbih4KSBzdHJzcGxpdCh4LCBzcGxpdD0iICIpW1sxXV0pCgp0dGVzdF9tYXRyaXggPC0gc2FwcGx5KGNvbWJucywgZnVuY3Rpb24oeCl7CiAgdCA8LSB0LnRlc3Qod2lkZV9zcWxbW3hbMV1dXSwgd2lkZV9zcWxbW3hbMl1dXSkKICBjKHN0YXRpc3RpYyA9IHQkc3RhdGlzdGljLCBwX3ZhbHVlID0gdCRwLnZhbHVlKQogIAp9KQoKdHRlc3RfZGYgPC0gZGF0YS5mcmFtZSh4ID0gc2FwcGx5KGNvbWJucywgIlsiLCAxKSwKICAgICAgICAgICAgICAgICAgICAgICB5ID0gc2FwcGx5KGNvbWJucywgIlsiLCAyKSwKICAgICAgICAgICAgICAgICAgICAgICBzdGF0aXN0aWMgPSB0dGVzdF9tYXRyaXhbMSxdLAogICAgICAgICAgICAgICAgICAgICAgIHBfdmFsdWUgPSB0dGVzdF9tYXRyaXhbMixdKQoKdHRlc3RfZGYgPC0gd2l0aCh0dGVzdF9kZiwgdHRlc3RfZGZbb3JkZXIoeCwgeSksXSkKdHRlc3RfZGYKYGBgCgoKYGBge3IgZmlnLmhlaWdodCA9IDQsIGZpZy53aWR0aCA9IDEwLCBmaWcuYWxpZ24gPSAiY2VudGVyIn0KCmJ5KHR0ZXN0X2RmLCB0dGVzdF9kZiR4LCBmdW5jdGlvbihzdWIpCiAgCiAgZ2dwbG90KHN1YiwgYWVzKHgsIHN0YXRpc3RpYywgZmlsbD15KSkgKyBnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIpICsKICAgIGxhYnModGl0bGU9cGFzdGUwKCJDVEEgU3lzdGVtICdMJyBMaW5lcyAtIFBhaXJ3aXNlIFQtdGVzdHMgZm9yICIsIAogICAgICAgICAgICAgICAgICAgICB0b3VwcGVyKHN1YnN0cihzdWIkeFtbMV1dLCAxLCAxKSksIAogICAgICAgICAgICAgICAgICAgICBzdWJzdHIoc3ViJHhbWzFdXSwgMiwgbmNoYXIoYXMuY2hhcmFjdGVyKHN1YiR4W1sxXV0pKSksICIgTGluZSIpLCAKICAgICAgICAgeD0iUmFpbCBMaW5lIiwgeT0iVC10ZXN0IFN0YXQiKSArCiAgICBzY2FsZV94X2Rpc2NyZXRlKGV4cGFuZCA9IGMoMCwwKSkgKyAKICAgIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBsYWJlbD1jb21tYSkgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY3RhX3BhbGV0dGVbIW5hbWVzKGN0YV9wYWxldHRlKT09c3ViJHhbWzFdXV0pICsgCiAgICBndWlkZXMoZmlsbD1ndWlkZV9sZWdlbmQoIlJhaWwgTGluZXMiLCBucm93PTEpKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIsCiAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0PTAuNSwgc2l6ZT0xOCwgY29sb3VyPWN0YV9wYWxldHRlW25hbWVzKGN0YV9wYWxldHRlKT09c3ViJHhbWzFdXV0pLAogICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9MCwgaGp1c3Q9MC41KSkKICApCgpgYGAKPC9kaXY+CgojIyA8c3BhbiBzdHlsZT0iY29sb3I6ICM2NDY0NjQiPkNTViBSZWdyZXNzaW9uPC9zcGFuPiAjIwoKIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzY0NjQ2NCI+UmlnaHQtSGFuZCBTaWRlIFZhcmlhYmxlczwvc3Bhbj4gIyMKCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzY0NjQ2NCI+QWdhaW4sIHJlcXVpcmVzIG1haW50ZW5hbmNlIGFuZCBzdG9yYWdlIG9uIGRpc2sgc3BhY2U8L3NwYW4+ICMjIwotICMjIyA8c3BhbiBzdHlsZT0iY29sb3I6ICM2NDY0NjQiPkFnYWluLCByZXF1aXJlcyBsb2FkIHRpbWUgZnJvbSBub24tY2VudHJhbGl6ZWQgcGF0aHM8L3NwYW4+ICMjIwotICMjIyA8c3BhbiBzdHlsZT0iY29sb3I6ICM2NDY0NjQiPkFnYWluLCByZXF1aXJlcyBhbnkgYWQtaG9jIG11bmdpbmcgZm9yIHVzYWJpbGl0eTwvc3Bhbj4gIyMjCgpgYGB7cn0KCiMgU291cmNlOiBTdC4gTG91aXMgRmVkZXJhbCBSZXNlcnZlIEJhbmsKdW5lbXBsb3ltZW50X2RmIDwtIHJlYWQuY3N2KCdDaGljYWdvX1VuZW1wbG95bWVudF9SYXRlcy5jc3YnKQp1bmVtcGxveW1lbnRfZGYkRGF0ZSA8LSBhcy5EYXRlKHVuZW1wbG95bWVudF9kZiREYXRlLCBmb3JtYXQ9JyVZLSVtLSVkJywgb3JpZ2luPScxOTcwLTAxLTAxJykKaGVhZCh1bmVtcGxveW1lbnRfZGYpCgojIFNvdXJjZTogVS5TLiBEZXBhcnRtZW50IG9mIEVuZXJneTogRUlBCmdhc19wcmljZXNfZGYgPC0gcmVhZC5jc3YoJ1VTX0dhc19QcmljZXMuY3N2JykKZ2FzX3ByaWNlc19kZiREYXRlIDwtIGFzLkRhdGUoZ2FzX3ByaWNlc19kZiREYXRlLCBmb3JtYXQ9JyVZLSVtLSVkJywgb3JpZ2luPScxOTcwLTAxLTAxJykKaGVhZChnYXNfcHJpY2VzX2RmKQoKIyBTb3VyY2U6IFUuUy4gTmF0aW9uYWwgT2NlYW5pYyBhbmQgQXRtb3NwaGVyaWMgQWRtaW5pc3RyYXRpb24gKE5PQUEpCndlYXRoZXJfZGYgPC0gcmVhZC5jc3YoJ0NoaWNhZ29fV2VhdGhlcl9EYXRhLmNzdicpCndlYXRoZXJfZGYkRGF0ZSA8LSBhcy5EYXRlKHdlYXRoZXJfZGYkRGF0ZSwgZm9ybWF0PSclWS0lbS0lZCcsIG9yaWdpbj0nMTk3MC0wMS0wMScpCmhlYWQod2VhdGhlcl9kZikKYGBgCgoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5CdXMgTW9kZWwgRGF0YTwvc3Bhbj4KCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzY0NjQ2NCI+RWNob2VzIHNldC1iYXNlZCBqb2luczwvc3Bhbj4gIyMjCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzY0NjQ2NCI+UmVwZXRpdGl2ZSBzb3VyY2luZyAgb2Ygc2FtZSBvYmplY3Q8L3NwYW4+ICMjIwotICMjIyA8c3BhbiBzdHlsZT0iY29sb3I6ICM2NDY0NjQiPk5lc3RlZCBkZW5zZSBwcm9jZXNzaW5nIG9mIHN0ZXBzPC9zcGFuPiAjIyMKCmBgYHtyfQpidXNfbW9kZWxfZGF0YSA8LSBtZXJnZSh1bmlxdWUoYnVzX21vbnRoX2NzdltjKCJyb3V0ZSIsICJyb3V0ZW5hbWUiKV0pLCBidXNfcmlkZXNfY3N2LCBieT0icm91dGUiKQoKYnVzX21vZGVsX2RhdGEgPC0gbWVyZ2UoYnVzX21vZGVsX2RhdGEsIHVuZW1wbG95bWVudF9kZiwgYnkueD0nZGF0ZScsIGJ5Lnk9J0RhdGUnKQpidXNfbW9kZWxfZGF0YSA8LSBtZXJnZShidXNfbW9kZWxfZGF0YSwgZ2FzX3ByaWNlc19kZiwgYnkueD0nZGF0ZScsIGJ5Lnk9J0RhdGUnKQpidXNfbW9kZWxfZGF0YSA8LSBtZXJnZShidXNfbW9kZWxfZGF0YSwgd2VhdGhlcl9kZiwgYnkueD0nZGF0ZScsIGJ5Lnk9J0RhdGUnKQoKaGVhZChidXNfbW9kZWxfZGF0YSkKYGBgCgoKIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzY0NjQ2NCI+QWRkIFNlYXNvbnM8L3NwYW4+CgpgYGB7cn0KYnVzX21vZGVsX2RhdGEkbm9ybWFsaXplZF9kdCA8LSBhcy5QT1NJWGx0KGJ1c19tb2RlbF9kYXRhJGRhdGUpCmJ1c19tb2RlbF9kYXRhJG5vcm1hbGl6ZWRfZHQkeWVhciA8LSBidXNfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0JHllYXIgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKDIwOTkgLSBhcy5pbnRlZ2VyKGZvcm1hdChidXNfbW9kZWxfZGF0YSRkYXRlLCAiJVkiKSkpCmJ1c19tb2RlbF9kYXRhJG5vcm1hbGl6ZWRfZHQgPC0gYXMuRGF0ZShidXNfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0KQoKCmJ1c19tb2RlbF9kYXRhJHNlYXNvbiA8LSBpZmVsc2UoYnVzX21vZGVsX2RhdGEkbm9ybWFsaXplZF9kdCA+PSAnMjA5OS0wMS0wMScgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBidXNfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0ICA8ICcyMDk5LTAzLTE5JywgJ3dpbnRlcicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGJ1c19tb2RlbF9kYXRhJG5vcm1hbGl6ZWRfZHQgPj0gJzIwOTktMDMtMjAnICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ1c19tb2RlbF9kYXRhJG5vcm1hbGl6ZWRfZHQgIDwgJzIwOTktMDYtMTknLCAnc3ByaW5nJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGJ1c19tb2RlbF9kYXRhJG5vcm1hbGl6ZWRfZHQgPj0gJzIwOTktMDYtMjAnICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBidXNfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0ICA8ICcyMDk5LTA5LTE5JywgJ3N1bW1lcicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoYnVzX21vZGVsX2RhdGEkbm9ybWFsaXplZF9kdCA+PSAnMjA5OS0wOS0yMCcgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBidXNfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0ICA8ICcyMDk5LTEyLTE5JywgJ2ZhbGwnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShidXNfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0ID49ICcyMDk5LTEyLTIwJyAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ1c19tb2RlbF9kYXRhJG5vcm1hbGl6ZWRfZHQgIDwgJzIwOTktMTItMzEnLCAnd2ludGVyJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTkEpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIApidXNfbW9kZWxfZGF0YVtzYW1wbGUobnJvdyhidXNfbW9kZWxfZGF0YSksIDEwKSwgYygibm9ybWFsaXplZF9kdCIsICJkYXRlIiwgInNlYXNvbiIpXQpidXNfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0IDwtIE5VTEwKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6ICM2NDY0NjQiPkJ1cyBNb2RlbGluZyAoT3JkaW5hcnkgTGVhc3QgU3F1YXJlcyk8L3NwYW4+CgpgYGB7cn0KbW9kZWwgPC0gbG0ocmlkZXMgfiBkYXl0eXBlICsgc2Vhc29uICsgVUVfUmF0ZSArIEdhc19QcmljZSArIEF2Z1RlbXAgKyBQcmVjaXBpdGF0aW9uICsgU25vd0RlcHRoLAogICAgICAgICAgICBkYXRhID0gYnVzX21vZGVsX2RhdGEpCgpzdW1tYXJ5KG1vZGVsKQpgYGAKCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzY0NjQ2NCI+UmFpbCBNb2RlbCBEYXRhPC9zcGFuPgoKIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogIzY0NjQ2NCI+QXNzaWduIExhdGl0dWRlIGFuZCBMb25naXR1ZGU8L3NwYW4+CgpgYGB7cn0KCnJhaWxfbG9uZyRsYXRpdHVkZSA8LSBhcy5udW1lcmljKGdzdWIoIlxcKCIsICIiLCBnc3ViKCIsIiwgIiIsIHNhcHBseShhcy5jaGFyYWN0ZXIocmFpbF9sb25nJGxvY2F0aW9uKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uKHgpIHN0cnNwbGl0KHgsIHNwbGl0PSJcXHMrIilbWzFdXVsxXSkpKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKCnJhaWxfbG9uZyRsb25naXR1ZGUgPC0gYXMubnVtZXJpYyhnc3ViKCJcXCkiLCAiIiwgc2FwcGx5KGFzLmNoYXJhY3RlcihyYWlsX2xvbmckbG9jYXRpb24pLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbih4KSBzdHJzcGxpdCh4LCBzcGxpdD0iXFxzKyIpW1sxXV1bMl0pKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQoKcmFpbF9sb25nW3NhbXBsZShucm93KHJhaWxfbG9uZyksIDEwKSwgYygibG9jYXRpb24iLCAibGF0aXR1ZGUiLCAibG9uZ2l0dWRlIildCmBgYAoKYGBge3J9CnJhaWxfbW9kZWxfZGF0YSA8LSBtZXJnZShyYWlsX2xvbmcsIHJhaWxfcmlkZXNfY3N2LCBieT0ic3RhdGlvbl9pZCIpCgpyYWlsX21vZGVsX2RhdGEkcmF3IDwtIHJhaWxfbW9kZWxfZGF0YSRyaWRlcwoKcmFpbF9tb2RlbF9kYXRhJHJpZGVzIDwtIHdpdGgocmFpbF9tb2RlbF9kYXRhLCByaWRlcyAvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdmUoc3RhdGlvbl9pZCwgc3RhdGlvbl9pZCwgZGF0ZSwgRlVOPWxlbmd0aCkpCgpyYWlsX21vZGVsX2RhdGEgPC0gbWVyZ2UocmFpbF9tb2RlbF9kYXRhLCB1bmVtcGxveW1lbnRfZGYsIGJ5Lng9J2RhdGUnLCBieS55PSdEYXRlJykKcmFpbF9tb2RlbF9kYXRhIDwtIG1lcmdlKHJhaWxfbW9kZWxfZGF0YSwgZ2FzX3ByaWNlc19kZiwgYnkueD0nZGF0ZScsIGJ5Lnk9J0RhdGUnKQpyYWlsX21vZGVsX2RhdGEgPC0gbWVyZ2UocmFpbF9tb2RlbF9kYXRhLCB3ZWF0aGVyX2RmLCBieS54PSdkYXRlJywgYnkueT0nRGF0ZScpCgpoZWFkKHJhaWxfbW9kZWxfZGF0YSwgMTApCmBgYAoKCiMjIyA8c3BhbiBzdHlsZT0iY29sb3I6ICM2NDY0NjQiPkFkZCBTZWFzb25zPC9zcGFuPgoKYGBge3J9CnJhaWxfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0IDwtIGFzLlBPU0lYbHQocmFpbF9tb2RlbF9kYXRhJGRhdGUpCnJhaWxfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0JHllYXIgPC0gcmFpbF9tb2RlbF9kYXRhJG5vcm1hbGl6ZWRfZHQkeWVhciArCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoMjA5OSAtIGFzLmludGVnZXIoZm9ybWF0KHJhaWxfbW9kZWxfZGF0YSRkYXRlLCAiJVkiKSkpCnJhaWxfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0IDwtIGFzLkRhdGUocmFpbF9tb2RlbF9kYXRhJG5vcm1hbGl6ZWRfZHQpCgoKcmFpbF9tb2RlbF9kYXRhJHNlYXNvbiA8LSBpZmVsc2UocmFpbF9tb2RlbF9kYXRhJG5vcm1hbGl6ZWRfZHQgPj0gJzIwOTktMDEtMDEnICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmFpbF9tb2RlbF9kYXRhJG5vcm1hbGl6ZWRfZHQgIDwgJzIwOTktMDMtMTknLCAnd2ludGVyJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UocmFpbF9tb2RlbF9kYXRhJG5vcm1hbGl6ZWRfZHQgPj0gJzIwOTktMDMtMjAnICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJhaWxfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0ICA8ICcyMDk5LTA2LTE5JywgJ3NwcmluZycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShyYWlsX21vZGVsX2RhdGEkbm9ybWFsaXplZF9kdCA+PSAnMjA5OS0wNi0yMCcgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJhaWxfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0ICA8ICcyMDk5LTA5LTE5JywgJ3N1bW1lcicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UocmFpbF9tb2RlbF9kYXRhJG5vcm1hbGl6ZWRfZHQgPj0gJzIwOTktMDktMjAnICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmFpbF9tb2RlbF9kYXRhJG5vcm1hbGl6ZWRfZHQgIDwgJzIwOTktMTItMTknLCAnZmFsbCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHJhaWxfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0ID49ICcyMDk5LTEyLTIwJyAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJhaWxfbW9kZWxfZGF0YSRub3JtYWxpemVkX2R0ICA8ICcyMDk5LTEyLTMxJywgJ3dpbnRlcicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5BKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKcmFpbF9tb2RlbF9kYXRhW3NhbXBsZShucm93KHJhaWxfbW9kZWxfZGF0YSksIDEwKSwgYygibm9ybWFsaXplZF9kdCIsICJkYXRlIiwgInNlYXNvbiIpXQpyYWlsX21vZGVsX2RhdGEkbm9ybWFsaXplZF9kdCA8LSBOVUxMCgpgYGAKCgoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNjQ2NDY0Ij5SYWlsIE1vZGVsaW5nIChPcmRpbmFyeSBMZWFzdCBTcXVhcmVzKTwvc3Bhbj4KCmBgYHtyfQptb2RlbCA8LSBsbShyaWRlcyB+IGRheXR5cGUgKyBzZWFzb24gKyBsYXRpdHVkZSArIGxvbmdpdHVkZSArIHJhaWxfbGluZSArIAogICAgICAgICAgICAgICAgICAgIFVFX1JhdGUgKyBHYXNfUHJpY2UgKyBBdmdUZW1wICsgUHJlY2lwaXRhdGlvbiArIFNub3dEZXB0aCwgCiAgICAgICAgICAgIGRhdGEgPSByYWlsX21vZGVsX2RhdGEpCgpzdW1tYXJ5KG1vZGVsKQpgYGAKCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjojMzM2NzkxIj5TUUwgUmVncmVzc2lvbjwvc3Bhbj4KCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjojMzM2NzkxIj5BZHZhbmNlZCBwcmVwYXJhdGlvbiBvZiBkYXRhPC9zcGFuPiAjIyMKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPk1hdGVyaWFsaXplZCB2aWV3IGZhY2lsaXRhdGVzIHJlcHJvZHVjaWJsZSByZXNlYXJjaDwvc3Bhbj4gIyMjCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjojMzM2NzkxIj5Db21wYWN0IGFuZCBzdHJhaWdodGZvcndhcmQgZGF0YSBzb3VyY2luZzwvc3Bhbj4gIyMjCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IzMzNjc5MSI+QnVzIE1vZGVsaW5nIERhdGE8L3NwYW4+Cgo8ZGl2IGNsYXNzPSJibHVlIj4KYGBge3NxbCwgZXZhbD1GQUxTRX0KQ1JFQVRFIE1BVEVSSUFMSVpFRCBWSUVXIEJ1c19Nb2RlbF9EYXRhIEFTCiAgICBTRUxFQ1QgYi5pZCwgYi5yb3V0ZV9pZCwgYi5yaWRlX2RhdGUsIGIuZGF5X3R5cGUsIGIucmlkZXMsIHIucm91dGVfbmFtZSwgCiAgICAgICAgICAgQ0FTRSAKICAgICAgICAgICAgICAgV0hFTiBiLm5vcm1hbGl6ZWRfZGF0ZSBCRVRXRUVOICcyMDk5LTAxLTAxJyBBTkQgJzIwOTktMDMtMTknIFRIRU4gJ3dpbnRlcicKICAgICAgICAgICAgICAgV0hFTiBiLm5vcm1hbGl6ZWRfZGF0ZSBCRVRXRUVOICcyMDk5LTAzLTIwJyBBTkQgJzIwOTktMDYtMTknIFRIRU4gJ3NwcmluZycKICAgICAgICAgICAgICAgV0hFTiBiLm5vcm1hbGl6ZWRfZGF0ZSBCRVRXRUVOICcyMDk5LTA2LTIwJyBBTkQgJzIwOTktMDktMTknIFRIRU4gJ3N1bW1lcicKICAgICAgICAgICAgICAgV0hFTiBiLm5vcm1hbGl6ZWRfZGF0ZSBCRVRXRUVOICcyMDk5LTA5LTIwJyBBTkQgJzIwOTktMTItMTknIFRIRU4gJ2ZhbGwnCiAgICAgICAgICAgICAgIFdIRU4gYi5ub3JtYWxpemVkX2RhdGUgQkVUV0VFTiAnMjA5OS0xMi0yMCcgQU5EICcyMDk5LTEyLTMxJyBUSEVOICd3aW50ZXInCiAgICAgICAgICAgICAgIEVMU0UgTlVMTAogICAgICAgICAgIEVORCBBcyBzZWFzb24sCiAgICAgICAgICAgdWUudWVfcmF0ZSwgZy5nYXNfcHJpY2UsIHcuYXZnX3RlbXAsIHcucHJlY2lwaXRhdGlvbiwgdy5zbm93X2RlcHRoCiAgICBGUk9NIAogICAgICgKICAgICAgU0VMRUNUIGlkLCByb3V0ZV9pZCwgZGF5X3R5cGUsIHJpZGVzLCByaWRlX2RhdGUsIAogICAgICAgICAgICAgcmlkZV9kYXRlICsgKDIwOTkgLSBkYXRlX3BhcnQoJ3llYXInLCByaWRlX2RhdGUpICB8fCcgeWVhcicpOjppbnRlcnZhbCBhcyBub3JtYWxpemVkX2RhdGUKICAgICAgRlJPTSBidXNfcmlkZXMKICAgICApIGIKICAgIElOTkVSIEpPSU4gYnVzX3JvdXRlcyByIE9OIGIucm91dGVfaWQgPSByLnJvdXRlX2lkCiAgICBJTk5FUiBKT0lOIHVuZW1wbG95bWVudF9yYXRlcyB1ZSBPTiB1ZS51ZV9kYXRlID0gYi5yaWRlX2RhdGUKICAgIElOTkVSIEpPSU4gZ2FzX3ByaWNlcyBnIE9OIGcuZ2FzX2RhdGUgPSBiLnJpZGVfZGF0ZQogICAgSU5ORVIgSk9JTiB3ZWF0aGVyX2RhdGEgdyBPTiB3LndlYXRoZXJfZGF0ZSA9IGIucmlkZV9kYXRlCiAgICBPUkRFUiBCWSBiLnJpZGVfZGF0ZSwgTlVMTElGKHJlZ2V4cF9yZXBsYWNlKGIucm91dGVfaWQsICdcRCcsICcnLCAnZycpLCAnJyk6OmludDsKICAgIApSRUZSRVNIIE1BVEVSSUFMSVpFRCBWSUVXIEJ1c19Nb2RlbF9EYXRhOwpgYGAKCmBgYHtyfQpidXNfbW9kZWxfZGF0YSA8LSBkYkdldFF1ZXJ5KGNvbm4sICJTRUxFQ1QgKiBGUk9NIGJ1c19tb2RlbF9kYXRhIikKCmhlYWQoYnVzX21vZGVsX2RhdGEpCmBgYAo8L2Rpdj4KCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjojMzM2NzkxIj5CdXMgTW9kZWxpbmcgKE9yZGluYXJ5IExlYXN0IFNxdWFyZXMpPC9zcGFuPgoKPGRpdiBjbGFzcz0iYmx1ZSI+CmBgYHtyfQptb2RlbCA8LSBsbShyaWRlcyB+IGRheV90eXBlICsgc2Vhc29uICsgdWVfcmF0ZSArIGdhc19wcmljZSArIGF2Z190ZW1wICsgcHJlY2lwaXRhdGlvbiArIHNub3dfZGVwdGgsCiAgICAgICAgICAgIGRhdGEgPSBidXNfbW9kZWxfZGF0YSkKCnN1bW1hcnkobW9kZWwpCmBgYAo8L2Rpdj4KCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjojMzM2NzkxIj5SYWlsIE1vZGVsaW5nIERhdGE8L3NwYW4+Cgo8ZGl2IGNsYXNzPSJibHVlIj4KYGBge3NxbCwgZXZhbD1GQUxTRX0KQ1JFQVRFIE1BVEVSSUFMSVpFRCBWSUVXIFJhaWxfTW9kZWxfRGF0YSBBUwogICAgU0VMRUNUIHIuaWQsIHIuc3RhdGlvbl9pZCwgci5zdGF0aW9uX25hbWUsIHIucmlkZV9kYXRlLCByLmRheV90eXBlLCByLnJpZGVzIEFTIHJhdywgCiAgICAgICAgICAoci5yaWRlcyAvIENPVU5UKCopIE9WRVIoUEFSVElUSU9OIEJZIHIuc3RhdGlvbl9pZCwgci5yaWRlX2RhdGUpKSBBUyByaWRlcywKICAgICAgICAgIENBU0UgCiAgICAgICAgICAgICAgIFdIRU4gci5ub3JtYWxpemVkX2RhdGUgQkVUV0VFTiAnMjA5OS0wMS0wMScgQU5EICcyMDk5LTAzLTE5JyBUSEVOICd3aW50ZXInCiAgICAgICAgICAgICAgIFdIRU4gci5ub3JtYWxpemVkX2RhdGUgQkVUV0VFTiAnMjA5OS0wMy0yMCcgQU5EICcyMDk5LTA2LTE5JyBUSEVOICdzcHJpbmcnCiAgICAgICAgICAgICAgIFdIRU4gci5ub3JtYWxpemVkX2RhdGUgQkVUV0VFTiAnMjA5OS0wNi0yMCcgQU5EICcyMDk5LTA5LTE5JyBUSEVOICdzdW1tZXInCiAgICAgICAgICAgICAgIFdIRU4gci5ub3JtYWxpemVkX2RhdGUgQkVUV0VFTiAnMjA5OS0wOS0yMCcgQU5EICcyMDk5LTEyLTE5JyBUSEVOICdmYWxsJwogICAgICAgICAgICAgICBXSEVOIHIubm9ybWFsaXplZF9kYXRlIEJFVFdFRU4gJzIwOTktMTItMjAnIEFORCAnMjA5OS0xMi0zMScgVEhFTiAnd2ludGVyJwogICAgICAgICAgICAgICBFTFNFIE5VTEwKICAgICAgICAgICBFTkQgQXMgc2Vhc29uLCAgICAgICAgCiAgICAgICAgICAgUkVQTEFDRShSRVBMQUNFKChyZWdleHBfc3BsaXRfdG9fYXJyYXkocy5sb2NhdGlvbiwgJ1xzKycpKVsxXSwgJywnLCAnJyksICcoJywgJycpOjpudW1lcmljIEFTIGxhdGl0dWRlLAogICAgICAgICAgIFJFUExBQ0UoKHJlZ2V4cF9zcGxpdF90b19hcnJheShzLmxvY2F0aW9uLCAnXHMrJykpWzJdLCAnKScsICcnKTo6bnVtZXJpYyBBUyBsb25naXR1ZGUsCiAgICAgICAgICAgcy5yYWlsX2xpbmUsIHMuYWRhLCBzLmRpcmVjdGlvbiwKICAgICAgICAgICB1ZS51ZV9yYXRlLCBnLmdhc19wcmljZSwgdy5hdmdfdGVtcCwgdy5wcmVjaXBpdGF0aW9uLCB3LnNub3dfZGVwdGgKICAgIEZST00gCiAgICAgICAoCiAgICAgICAgU0VMRUNUIGlkLCBzdGF0aW9uX2lkLCBzdGF0aW9uX25hbWUsIGRheV90eXBlLCByaWRlcywgcmlkZV9kYXRlLCAKICAgICAgICAgICAgICAgcmlkZV9kYXRlICsgKDIwOTkgLSBkYXRlX3BhcnQoJ3llYXInLCByaWRlX2RhdGUpICB8fCcgeWVhcicpOjppbnRlcnZhbCBhcyBub3JtYWxpemVkX2RhdGUKICAgICAgICBGUk9NIHJhaWxfcmlkZXMKICAgICAgIClyCiAgICBJTk5FUiBKT0lOIHJhaWxfc3RhdGlvbnMgcyBPTiBzLnN0YXRpb25faWQgPSByLnN0YXRpb25faWQKICAgIElOTkVSIEpPSU4gdW5lbXBsb3ltZW50X3JhdGVzIHVlIE9OIHVlLnVlX2RhdGUgPSByLnJpZGVfZGF0ZQogICAgSU5ORVIgSk9JTiBnYXNfcHJpY2VzIGcgT04gZy5nYXNfZGF0ZSA9IHIucmlkZV9kYXRlCiAgICBJTk5FUiBKT0lOIHdlYXRoZXJfZGF0YSB3IE9OIHcud2VhdGhlcl9kYXRlID0gci5yaWRlX2RhdGUKICAgIE9SREVSIEJZIHIucmlkZV9kYXRlLCByLnN0YXRpb25faWQ7CiAgICAKUkVGUkVTSCBNQVRFUklBTElaRUQgVklFVyBSYWlsX01vZGVsX0RhdGE7CmBgYAoKYGBge3J9CnJhaWxfbW9kZWxfZGF0YSA8LSBkYkdldFF1ZXJ5KGNvbm4sICJTRUxFQ1QgKiBGUk9NIHJhaWxfbW9kZWxfZGF0YSIpCgpoZWFkKHJhaWxfbW9kZWxfZGF0YSkKYGBgCjwvZGl2PgoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPlJhaWwgTW9kZWxpbmcgKE9yZGluYXJ5IExlYXN0IFNxdWFyZXMpPC9zcGFuPgoKPGRpdiBjbGFzcz0iYmx1ZSI+CmBgYHtyfQptb2RlbCA8LSBsbShyaWRlcyB+IGRheV90eXBlICsgc2Vhc29uICsgbGF0aXR1ZGUgKyBsb25naXR1ZGUgKyByYWlsX2xpbmUgKyAKICAgICAgICAgICAgICAgICAgICB1ZV9yYXRlICsgZ2FzX3ByaWNlICsgYXZnX3RlbXAgKyBwcmVjaXBpdGF0aW9uICsgc25vd19kZXB0aCwgCiAgICAgICAgICAgIGRhdGEgPSByYWlsX21vZGVsX2RhdGEpCgpzdW1tYXJ5KG1vZGVsKQpgYGAKPC9kaXY+CgpgYGB7cn0KIyBESVNDT05ORUNUIEZST00gREFUQUJBU0UKZGJEaXNjb25uZWN0KGNvbm4pCmBgYAoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPkNvbmNsdXNpb25zPC9zcGFuPiAjIwoKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPlBvc3RncmVzIHByb3ZpZGVzIGEgc3RhYmxlLCBjZW50cmFsaXplZCwgcmVwb3NpdG9yeSBmb3IgZGF0YSBzb3VyY2luZzwvc3Bhbj4gIyMjCi0gIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjojMzM2NzkxIj5Qb3N0Z3JlcyBtYWludGFpbnMgYW4gZXhwcmVzc2l2ZSBTUUwgZGlhbGVjdCBmb3IgZGF0YSBwcm9jZXNzaW5nPC9zcGFuPiAjIyMKLSAjIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiMzMzY3OTEiPlBvc3RncmVzIHN1cHBvcnRzIGRhdGEgc2NpZW5jZSB3aXRoIHZlY3Rvcml6ZWQgbWV0aG9kcyBhbmQgcmVwcm9kdWNpYmlsaXR5PC9zcGFuPiAjIyMKCgo=